first commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# 资源公共路径,需要以 /开头和结尾
|
||||
VITE_PUBLIC_PATH = './'
|
||||
|
||||
# 是否启用代理
|
||||
VITE_USE_PROXY = true
|
||||
|
||||
# base api
|
||||
VITE_BASE_API = '/api/v1'
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
VITE_BASE_API=.
|
||||
@@ -0,0 +1,22 @@
|
||||
logs
|
||||
*.log
|
||||
*.sh
|
||||
node_modules
|
||||
.DS_Store
|
||||
lib
|
||||
*.md
|
||||
*.scss
|
||||
*.woff
|
||||
*.ttf
|
||||
.vscode
|
||||
.idea
|
||||
dist
|
||||
mock
|
||||
public
|
||||
bin
|
||||
build
|
||||
config
|
||||
index.html
|
||||
components.d.ts
|
||||
element.d.ts
|
||||
src/assets
|
||||
@@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
extends: ['plugin:vue/vue3-recommended', 'plugin:prettier/recommended'],
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
ecmaVersion: 2022,
|
||||
ecmaFeatures: {
|
||||
jsx: true
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
curly: 'error', // 控制语句需要大括号
|
||||
'vue/multi-word-component-names': 'off'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
*-lock.yaml
|
||||
*.lock
|
||||
*-lock.json
|
||||
@@ -0,0 +1,2 @@
|
||||
# 使用pnpm,则需要
|
||||
shamefully-hoist=true
|
||||
@@ -0,0 +1,29 @@
|
||||
// 参考:https://prettier.io/docs/en/options.html
|
||||
module.exports = {
|
||||
// 超过指定值换行
|
||||
printWidth: 100,
|
||||
// 制表符宽度
|
||||
tabWidth: 2,
|
||||
// 使用制表符缩进
|
||||
// useTabs: true,
|
||||
// 句尾不使用分号
|
||||
semi: false,
|
||||
// 使用单引号
|
||||
singleQuote: true,
|
||||
// 在 JSX 文件中使用单引号代替双引号
|
||||
jsxSingleQuote: true,
|
||||
// 在对象或数组最后一个元素后面是否加逗号,可选值"[none:不追加 | es5:追加]
|
||||
trailingComma: 'none',
|
||||
// 在对象,数组括号与代码之间加空格 "{ foo: bar }"
|
||||
bracketSpacing: true,
|
||||
// 将多行 JSX 元素的 > 放置于最后一行的末尾,而非换行
|
||||
jsxBracketSameLine: true,
|
||||
//(x) => {} 箭头函数参数只有一个时是否要有小括号,可选值"[always:(x) => x | avoid:x => x]
|
||||
arrowParens: 'avoid',
|
||||
// 使用默认的折行标准,可选值"[always|never|preserve]"
|
||||
proseWrap: 'preserve',
|
||||
// 指定HTML文件的全局空格敏感度,可选值"[css|strict|ignore]"
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
// 结尾时换行符,默认值lf,可选值"[auto|lf|crlf|cr]"
|
||||
endOfLine: 'lf'
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# maku-generator-ui
|
||||
|
||||
## Project setup
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
/**
|
||||
* * 此处定义的是全局常量,启动或打包后将添加到window中
|
||||
* https://vitejs.cn/config/#define
|
||||
*/
|
||||
|
||||
// 项目构建时间
|
||||
const _BUILD_TIME_ = JSON.stringify(dayjs().format('YYYY-MM-DD HH:mm:ss'))
|
||||
|
||||
export const viteDefine = {
|
||||
_BUILD_TIME_,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './define'
|
||||
@@ -0,0 +1,23 @@
|
||||
export const OUTPUT_DIR = 'dist'
|
||||
|
||||
export const PROXY_CONFIG = {
|
||||
// /**
|
||||
// * @desc 替换匹配值
|
||||
// * @请求路径 http://localhost:3100/api/user
|
||||
// * @转发路径 http://localhost:9999/api/v1 +/user
|
||||
// */
|
||||
// '/api': {
|
||||
// target: 'http://localhost:9999/api/v1',
|
||||
// changeOrigin: true,
|
||||
// rewrite: (path) => path.replace(new RegExp('^/api'), ''),
|
||||
// },
|
||||
/**
|
||||
* @desc 不替换匹配值
|
||||
* @请求路径 http://localhost:3100/api/v1/user
|
||||
* @转发路径 http://localhost:9999/api/v1/user
|
||||
*/
|
||||
'/api/v1': {
|
||||
target: 'http://127.0.0.1:9999',
|
||||
changeOrigin: true,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createHtmlPlugin } from 'vite-plugin-html'
|
||||
|
||||
export function configHtmlPlugin(viteEnv, isBuild) {
|
||||
const { VITE_TITLE } = viteEnv
|
||||
|
||||
const htmlPlugin = createHtmlPlugin({
|
||||
minify: isBuild,
|
||||
inject: {
|
||||
data: {
|
||||
title: VITE_TITLE,
|
||||
},
|
||||
},
|
||||
})
|
||||
return htmlPlugin
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
/**
|
||||
* * unocss插件,原子css
|
||||
* https://github.com/antfu/unocss
|
||||
*/
|
||||
// import Unocss from 'unocss/vite'
|
||||
|
||||
// // rollup打包分析插件
|
||||
// import visualizer from 'rollup-plugin-visualizer'
|
||||
// // 压缩
|
||||
// import viteCompression from 'vite-plugin-compression'
|
||||
|
||||
// import { configHtmlPlugin } from './html'
|
||||
// import unplugin from './unplugin'
|
||||
|
||||
export function createVitePlugins(viteEnv, isBuild) {
|
||||
const plugins = [
|
||||
vue(),
|
||||
createSvgIconsPlugin({
|
||||
iconDirs: [resolve(__dirname, 'src/icons/svg')],
|
||||
symbolId: 'icon-[dir]-[name]'
|
||||
})
|
||||
]
|
||||
|
||||
if (viteEnv.VITE_USE_COMPRESS) {
|
||||
plugins.push(viteCompression({ algorithm: viteEnv.VITE_COMPRESS_TYPE || 'gzip' }))
|
||||
}
|
||||
|
||||
if (isBuild) {
|
||||
plugins.push(
|
||||
visualizer({
|
||||
open: true,
|
||||
gzipSize: true,
|
||||
brotliSize: true
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return plugins
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { resolve } from 'path'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
||||
import { FileSystemIconLoader } from 'unplugin-icons/loaders'
|
||||
import IconsResolver from 'unplugin-icons/resolver'
|
||||
|
||||
/**
|
||||
* * unplugin-icons插件,自动引入iconify图标
|
||||
* usage: https://github.com/antfu/unplugin-icons
|
||||
* 图标库: https://icones.js.org/
|
||||
*/
|
||||
import Icons from 'unplugin-icons/vite'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
|
||||
import { getSrcPath } from '../utils'
|
||||
|
||||
const customIconPath = resolve(getSrcPath(), 'assets/svg')
|
||||
|
||||
export default [
|
||||
AutoImport({
|
||||
imports: ['vue', 'vue-router'],
|
||||
dts: false,
|
||||
}),
|
||||
Icons({
|
||||
compiler: 'vue3',
|
||||
customCollections: {
|
||||
custom: FileSystemIconLoader(customIconPath),
|
||||
},
|
||||
scale: 1,
|
||||
defaultClass: 'inline-block',
|
||||
}),
|
||||
Components({
|
||||
resolvers: [
|
||||
NaiveUiResolver(),
|
||||
IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' }),
|
||||
],
|
||||
dts: false,
|
||||
}),
|
||||
createSvgIconsPlugin({
|
||||
iconDirs: [customIconPath],
|
||||
symbolId: 'icon-custom-[dir]-[name]',
|
||||
inject: 'body-last',
|
||||
customDomId: '__CUSTOM_SVG_ICON__',
|
||||
}),
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
import { resolve } from 'path'
|
||||
import chalk from 'chalk'
|
||||
import { writeFileSync } from 'fs-extra'
|
||||
import { OUTPUT_DIR } from '../constant'
|
||||
import { getEnvConfig, getRootPath } from '../utils'
|
||||
|
||||
export function runBuildCNAME() {
|
||||
const { VITE_CNAME } = getEnvConfig()
|
||||
if (!VITE_CNAME) return
|
||||
try {
|
||||
writeFileSync(resolve(getRootPath(), `${OUTPUT_DIR}/CNAME`), VITE_CNAME)
|
||||
} catch (error) {
|
||||
console.log(chalk.red('CNAME file failed to package:\n' + error))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import chalk from 'chalk'
|
||||
import { runBuildCNAME } from './build-cname'
|
||||
|
||||
export const runBuild = async () => {
|
||||
try {
|
||||
runBuildCNAME()
|
||||
console.log(`✨ ${chalk.cyan('build successfully!')}`)
|
||||
} catch (error) {
|
||||
console.log(chalk.red('vite build error:\n' + error))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
runBuild()
|
||||
@@ -0,0 +1,70 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
/**
|
||||
* * 项目根路径
|
||||
* @descrition 结尾不带/
|
||||
*/
|
||||
export function getRootPath() {
|
||||
return path.resolve(process.cwd())
|
||||
}
|
||||
|
||||
/**
|
||||
* * 项目src路径
|
||||
* @param srcName src目录名称(默认: "src")
|
||||
* @descrition 结尾不带斜杠
|
||||
*/
|
||||
export function getSrcPath(srcName = 'src') {
|
||||
return path.resolve(getRootPath(), srcName)
|
||||
}
|
||||
|
||||
export function convertEnv(envOptions) {
|
||||
const result = {}
|
||||
if (!envOptions) return result
|
||||
|
||||
for (const envKey in envOptions) {
|
||||
let envVal = envOptions[envKey]
|
||||
if (['true', 'false'].includes(envVal)) envVal = envVal === 'true'
|
||||
|
||||
if (['VITE_PORT'].includes(envKey)) envVal = +envVal
|
||||
|
||||
result[envKey] = envVal
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前环境下生效的配置文件名
|
||||
*/
|
||||
function getConfFiles() {
|
||||
const script = process.env.npm_lifecycle_script
|
||||
const reg = new RegExp('--mode ([a-z_\\d]+)')
|
||||
const result = reg.exec(script)
|
||||
if (result) {
|
||||
const mode = result[1]
|
||||
return ['.env', '.env.local', `.env.${mode}`]
|
||||
}
|
||||
return ['.env', '.env.local', '.env.production']
|
||||
}
|
||||
|
||||
export function getEnvConfig(match = 'VITE_', confFiles = getConfFiles()) {
|
||||
let envConfig = {}
|
||||
confFiles.forEach((item) => {
|
||||
try {
|
||||
if (fs.existsSync(path.resolve(process.cwd(), item))) {
|
||||
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)))
|
||||
envConfig = { ...envConfig, ...env }
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error in parsing ${item}`, e)
|
||||
}
|
||||
})
|
||||
const reg = new RegExp(`^(${match})`)
|
||||
Object.keys(envConfig).forEach((key) => {
|
||||
if (!reg.test(key)) {
|
||||
Reflect.deleteProperty(envConfig, key)
|
||||
}
|
||||
})
|
||||
return envConfig
|
||||
}
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
ElAside: (typeof import('element-plus/es'))['ElAside']
|
||||
ElAvatar: (typeof import('element-plus/es'))['ElAvatar']
|
||||
ElButton: (typeof import('element-plus/es'))['ElButton']
|
||||
ElCard: (typeof import('element-plus/es'))['ElCard']
|
||||
ElConfigProvider: (typeof import('element-plus/es'))['ElConfigProvider']
|
||||
ElContainer: (typeof import('element-plus/es'))['ElContainer']
|
||||
ElDropdown: (typeof import('element-plus/es'))['ElDropdown']
|
||||
ElDropdownItem: (typeof import('element-plus/es'))['ElDropdownItem']
|
||||
ElDropdownMenu: (typeof import('element-plus/es'))['ElDropdownMenu']
|
||||
ElHeader: (typeof import('element-plus/es'))['ElHeader']
|
||||
ElIcon: (typeof import('element-plus/es'))['ElIcon']
|
||||
ElImage: (typeof import('element-plus/es'))['ElImage']
|
||||
ElLink: (typeof import('element-plus/es'))['ElLink']
|
||||
ElMain: (typeof import('element-plus/es'))['ElMain']
|
||||
ElMenu: (typeof import('element-plus/es'))['ElMenu']
|
||||
ElMenuItem: (typeof import('element-plus/es'))['ElMenuItem']
|
||||
ElScrollbar: (typeof import('element-plus/es'))['ElScrollbar']
|
||||
ElSubMenu: (typeof import('element-plus/es'))['ElSubMenu']
|
||||
ElTabPane: (typeof import('element-plus/es'))['ElTabPane']
|
||||
ElTabs: (typeof import('element-plus/es'))['ElTabs']
|
||||
Lang: (typeof import('./src/components/Lang/index.vue'))['default']
|
||||
RouterLink: (typeof import('vue-router'))['RouterLink']
|
||||
RouterView: (typeof import('vue-router'))['RouterView']
|
||||
SvgIcon: (typeof import('./src/components/SvgIcon/index.vue'))['default']
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "maku-generator-ui",
|
||||
"version": "3.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint --fix --ext .vue,.jsx,.ts,.tsx ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@element-plus/icons-vue": "2.3.1",
|
||||
"@vueuse/core": "9.1.1",
|
||||
"axios": "1.6.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"element-plus": "2.7.6",
|
||||
"qs": "6.10.3",
|
||||
"sortablejs": "^1.15.0",
|
||||
"vue": "3.4.30",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
"vue-router": "4.2.5",
|
||||
"vxe-table": "^4.3.5",
|
||||
"xe-utils": "^3.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "7.24.0",
|
||||
"@types/node": "20.11.28",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@types/qs": "6.9.12",
|
||||
"@types/sm-crypto": "0.3.4",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@vitejs/plugin-vue": "5.0.5",
|
||||
"@vue/compiler-sfc": "3.4.30",
|
||||
"@vue/eslint-config-prettier": "9.0.0",
|
||||
"@vue/eslint-config-typescript": "13.0.0",
|
||||
"@vue/tsconfig": "0.5.1",
|
||||
"dotenv": "^16.5.0",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-vue": "9.23.0",
|
||||
"prettier": "3.2.5",
|
||||
"sass": "1.72.0",
|
||||
"typescript": "5.4.2",
|
||||
"vite": "5.3.1",
|
||||
"vite-plugin-svg-icons": "2.0.1",
|
||||
"vite-plugin-vue-setup-extend": "0.4.0",
|
||||
"vue-tsc": "2.0.22"
|
||||
},
|
||||
"keywords": [
|
||||
"vue",
|
||||
"vue3",
|
||||
"vuejs",
|
||||
"vite",
|
||||
"element-plus",
|
||||
"generator",
|
||||
"maku-generator"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<el-config-provider :locale="zh_cn">
|
||||
<router-view />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import zh_cn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import { RouterView } from 'vue-router'
|
||||
import { useTitle } from '@vueuse/core'
|
||||
|
||||
// 设置标题
|
||||
useTitle('Maku Generator')
|
||||
</script>
|
||||
@@ -0,0 +1,17 @@
|
||||
import service from '@/utils/request'
|
||||
|
||||
export const useBaseClassApi = (id: Number) => {
|
||||
return service.get('/gen/baseclass/' + id)
|
||||
}
|
||||
|
||||
export const useBaseClassListApi = () => {
|
||||
return service.get('/gen/baseclass/list')
|
||||
}
|
||||
|
||||
export const useBaseClassSubmitApi = (dataForm: any) => {
|
||||
if (dataForm.id) {
|
||||
return service.put('/gen/baseclass', dataForm)
|
||||
} else {
|
||||
return service.post('/gen/baseclass', dataForm)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import service from '@/utils/request'
|
||||
|
||||
export const useDataSourceTestApi = (id: Number) => {
|
||||
return service.get('/gen/datasource/test/' + id)
|
||||
}
|
||||
|
||||
export const useDataSourceApi = (id: Number) => {
|
||||
return service.get('/gen/datasource/' + id)
|
||||
}
|
||||
|
||||
export const useDataSourceListApi = () => {
|
||||
return service.get('/gen/datasource/list')
|
||||
}
|
||||
|
||||
export const useDataSourceSubmitApi = (dataForm: any) => {
|
||||
if (dataForm.id) {
|
||||
return service.put('/gen/datasource', dataForm)
|
||||
} else {
|
||||
return service.post('/gen/datasource', dataForm)
|
||||
}
|
||||
}
|
||||
|
||||
export const useDataSourceTableListApi = (id: string, tableName: string) => {
|
||||
return service.get(`/gen/datasource/get/${id}?tableName=${tableName}`)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import service from '@/utils/request'
|
||||
|
||||
export const useFieldTypeApi = (id: Number) => {
|
||||
return service.get('/gen/fieldtype/' + id)
|
||||
}
|
||||
|
||||
export const useFieldTypeListApi = () => {
|
||||
return service.get('/gen/fieldtype/list')
|
||||
}
|
||||
|
||||
export const useFieldTypeSubmitApi = (dataForm: any) => {
|
||||
if (dataForm.id) {
|
||||
return service.put('/gen/fieldtype', dataForm)
|
||||
} else {
|
||||
return service.post('/gen/fieldtype', dataForm)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import service from '@/utils/request'
|
||||
|
||||
// 生成代码(zip压缩包)
|
||||
export const useDownloadApi = (tableIds: any[]) => {
|
||||
location.href =
|
||||
import.meta.env.VITE_BASE_API + '/gen/generator/download?tableIds=' + tableIds.join(',')
|
||||
}
|
||||
|
||||
// 生成代码(自定义目录)
|
||||
export const useGeneratorApi = (tableIds: any[]) => {
|
||||
return service.post(`/gen/generator/code`, tableIds)
|
||||
}
|
||||
|
||||
//代码预览
|
||||
export const usePreviewApi = (tableId: any) => {
|
||||
return service.get(`/gen/table/preview/${tableId}`)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import service from '@/utils/request'
|
||||
|
||||
export const useProjectApi = (id: number) => {
|
||||
return service.get('/gen/project/' + id)
|
||||
}
|
||||
|
||||
export const useProjectSubmitApi = (dataForm: any) => {
|
||||
if (dataForm.id) {
|
||||
return service.put('/gen/project', dataForm)
|
||||
} else {
|
||||
return service.post('/gen/project', dataForm)
|
||||
}
|
||||
}
|
||||
|
||||
// 源码下载
|
||||
export const useSourceDownloadApi = (id: string) => {
|
||||
location.href = import.meta.env.VITE_BASE_API + '/gen/project/download/' + id
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import service from '@/utils/request'
|
||||
|
||||
export const useTableApi = (id: number) => {
|
||||
return service.get('/gen/table/' + id)
|
||||
}
|
||||
|
||||
export const useTableSubmitApi = (dataForm: any) => {
|
||||
return service.put('/gen/table', dataForm)
|
||||
}
|
||||
|
||||
export const useTableImportSubmitApi = (datasourceId: string, tableNameList: any) => {
|
||||
return service.post('/gen/table/import/' + datasourceId, tableNameList)
|
||||
}
|
||||
|
||||
export const useTableFieldSubmitApi = (tableId: number, fieldList: any) => {
|
||||
return service.put('/gen/table/field/' + tableId, fieldList)
|
||||
}
|
||||
|
||||
export const useTableSyncApi = (id: number) => {
|
||||
return service.post('/gen/table/sync/' + id)
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div style="width: 100%">
|
||||
<codemirror
|
||||
v-model="codes"
|
||||
placeholder="Code goes here..."
|
||||
:style="{ height }"
|
||||
:autofocus="true"
|
||||
:indent-with-tab="true"
|
||||
:tab-size="2"
|
||||
:extensions="extensions"
|
||||
@ready="handleReady"
|
||||
@change="change($event)"
|
||||
@focus="log('focus', $event)"
|
||||
@blur="log('blur', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, shallowRef, watch } from 'vue'
|
||||
import { Codemirror } from 'vue-codemirror'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '400px'
|
||||
}
|
||||
})
|
||||
const codes = ref('')
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
val => {
|
||||
codes.value = val || ''
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
// deep: true
|
||||
}
|
||||
)
|
||||
const extensions = [javascript()]
|
||||
const log = console.log
|
||||
|
||||
// Codemirror EditorView instance ref
|
||||
const view = shallowRef()
|
||||
const handleReady = payload => {
|
||||
view.value = payload.view
|
||||
}
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const change = event => {
|
||||
emit('update:modelValue', event)
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,4 @@
|
||||
import { withInstall } from '@/utils/tool'
|
||||
import SvgIcon from './src/svg-icon.vue'
|
||||
|
||||
export default withInstall(SvgIcon)
|
||||
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div class="svg-icon">
|
||||
<svg :class="`${className}`" :style="`color:${color};`" aria-hidden="true">
|
||||
<use :xlink:href="iconName" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
// https://www.iconfont.cn 图标库需使用前缀 icon- 才能匹配
|
||||
const iconName = computed(() => `#icon-${props.icon.replace('icon-', '')}`)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,164 @@
|
||||
import { IHooksOptions } from '@/hooks/interface'
|
||||
import service from '@/utils/request'
|
||||
import { onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
export const useCrud = (options: IHooksOptions) => {
|
||||
const defaultOptions: IHooksOptions = {
|
||||
createdIsNeed: true,
|
||||
dataListUrl: '',
|
||||
isPage: true,
|
||||
deleteUrl: '',
|
||||
primaryKey: 'id',
|
||||
exportUrl: '',
|
||||
queryForm: {},
|
||||
dataList: [],
|
||||
order: '',
|
||||
asc: false,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
pageSizes: [1, 10, 20, 50, 100, 200],
|
||||
dataListLoading: false,
|
||||
dataListSelections: []
|
||||
}
|
||||
|
||||
const mergeDefaultOptions = (options: any, props: any): IHooksOptions => {
|
||||
for (const key in options) {
|
||||
if (!Object.getOwnPropertyDescriptor(props, key)) {
|
||||
props[key] = options[key]
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
// 覆盖默认值
|
||||
const state = mergeDefaultOptions(defaultOptions, options)
|
||||
|
||||
onMounted(() => {
|
||||
if (state.createdIsNeed) {
|
||||
query()
|
||||
}
|
||||
})
|
||||
|
||||
const query = () => {
|
||||
if (!state.dataListUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
state.dataListLoading = true
|
||||
|
||||
service
|
||||
.get(state.dataListUrl, {
|
||||
params: {
|
||||
order: state.order,
|
||||
asc: state.asc,
|
||||
page: state.isPage ? state.page : null,
|
||||
limit: state.isPage ? state.limit : null,
|
||||
...state.queryForm
|
||||
}
|
||||
})
|
||||
.then((res: any) => {
|
||||
state.dataList = state.isPage ? res.data.list : res.data
|
||||
state.total = state.isPage ? res.data.total : 0
|
||||
})
|
||||
|
||||
state.dataListLoading = false
|
||||
}
|
||||
|
||||
const getDataList = () => {
|
||||
state.page = 1
|
||||
query()
|
||||
}
|
||||
|
||||
const sizeChangeHandle = (val: number) => {
|
||||
state.page = 1
|
||||
state.limit = val
|
||||
query()
|
||||
}
|
||||
|
||||
const currentChangeHandle = (val: number) => {
|
||||
state.page = val
|
||||
query()
|
||||
}
|
||||
|
||||
// 多选
|
||||
const selectionChangeHandle = (selections: any[]) => {
|
||||
state.dataListSelections = selections.map(
|
||||
(item: any) => state.primaryKey && item[state.primaryKey]
|
||||
)
|
||||
}
|
||||
|
||||
// 排序
|
||||
const sortChangeHandle = (data: any) => {
|
||||
const { prop, order } = data
|
||||
|
||||
if (prop && order) {
|
||||
state.order = prop
|
||||
state.asc = order === 'ascending'
|
||||
} else {
|
||||
state.order = ''
|
||||
}
|
||||
|
||||
query()
|
||||
}
|
||||
|
||||
const deleteHandle = (key: number | string) => {
|
||||
if (!state.deleteUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm('确定进行删除操作?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
service.delete(state.deleteUrl + '/' + key).then(() => {
|
||||
ElMessage.success('删除成功')
|
||||
|
||||
query()
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
const deleteBatchHandle = (key?: number | string) => {
|
||||
let data: any[] = []
|
||||
if (key) {
|
||||
data = [key]
|
||||
} else {
|
||||
data = state.dataListSelections ? state.dataListSelections : []
|
||||
|
||||
if (data.length === 0) {
|
||||
ElMessage.warning('请选择删除记录')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ElMessageBox.confirm('确定进行删除操作?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
if (state.deleteUrl) {
|
||||
service.delete(state.deleteUrl, { data: { data } }).then(() => {
|
||||
ElMessage.success('删除成功')
|
||||
query()
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
return {
|
||||
getDataList,
|
||||
sizeChangeHandle,
|
||||
currentChangeHandle,
|
||||
selectionChangeHandle,
|
||||
sortChangeHandle,
|
||||
deleteHandle,
|
||||
deleteBatchHandle
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
export interface IHooksOptions {
|
||||
// 否在创建页面时,调用数据列表接口
|
||||
createdIsNeed?: boolean
|
||||
// 数据列表 Url
|
||||
dataListUrl?: string
|
||||
// 是否需要分页
|
||||
isPage?: boolean
|
||||
// 删除 Url
|
||||
deleteUrl?: string
|
||||
// 主键key,用于删除场景
|
||||
primaryKey?: string
|
||||
// 导出 Url
|
||||
exportUrl?: string
|
||||
// 查询条件
|
||||
queryForm?: any
|
||||
// 数据列表
|
||||
dataList?: any[]
|
||||
// 排序字段
|
||||
order?: string
|
||||
// 是否升序
|
||||
asc?: boolean
|
||||
// 当前页码
|
||||
page?: number
|
||||
// 每页数
|
||||
limit?: number
|
||||
// 总条数
|
||||
total?: number
|
||||
pageSizes?: any[]
|
||||
// 数据列表,loading状态
|
||||
dataListLoading?: boolean
|
||||
// 数据列表,多选项
|
||||
dataListSelections?: any[]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M121.718 73.272v9.953c3.957-7.584 6.199-16.05 6.199-24.995C127.917 26.079 99.273 0 63.958 0 28.644 0 0 26.079 0 58.23c0 .403.028.806.028 1.21l22.97-25.953h13.34l-19.76 27.187h6.42V53.77l13.728-19.477v49.361H22.998V73.272H2.158c5.951 20.284 23.608 36.208 45.998 41.399-1.44 3.3-5.618 11.263-12.565 12.674-8.607 1.764 23.358.428 46.163-13.178 17.519-4.611 31.938-15.849 39.77-30.513h-13.506V73.272H85.02V59.464l22.998-25.977h13.008l-19.429 27.187h6.421v-7.433l13.727-19.402v39.433h-.027zm-78.24 2.822a10.516 10.516 0 0 1-.996-4.535V44.548c0-1.613.332-3.124.996-4.535a11.66 11.66 0 0 1 2.713-3.68c1.134-1.032 2.49-1.864 4.04-2.468 1.55-.605 3.21-.908 4.982-.908h11.292c1.77 0 3.431.303 4.981.908 1.522.604 2.85 1.41 3.986 2.418l-12.26 16.303v-2.898a1.96 1.96 0 0 0-.665-1.512c-.443-.403-.996-.604-1.66-.604-.665 0-1.218.201-1.661.604a1.96 1.96 0 0 0-.664 1.512v9.071L44.364 77.606a10.556 10.556 0 0 1-.886-1.512zm35.73-4.535c0 1.613-.332 3.124-.997 4.535a11.66 11.66 0 0 1-2.712 3.68c-1.134 1.032-2.49 1.864-4.04 2.469-1.55.604-3.21.907-4.982.907H55.185c-1.77 0-3.431-.303-4.981-.907-1.55-.605-2.906-1.437-4.041-2.47a12.49 12.49 0 0 1-1.384-1.512l13.727-18.217v6.375c0 .605.222 1.109.665 1.512.442.403.996.604 1.66.604.664 0 1.218-.201 1.66-.604a1.96 1.96 0 0 0 .665-1.512V53.87L75.97 36.838c.913.932 1.66 1.99 2.214 3.175.664 1.41.996 2.922.996 4.535v27.011h.028z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1651465026743" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1711" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
|
||||
</style></defs><path d="M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512z m259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.026 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.293-25.267z" fill="#ffffff" p-id="1712"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div class="sidebar-logo">
|
||||
<el-avatar src="./favicon.ico"></el-avatar>
|
||||
<span class="logo-title">Maku Generator</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sidebar-logo {
|
||||
width: var(--theme-aside-width) !important;
|
||||
height: var(--theme-header-height);
|
||||
line-height: var(--theme-header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: rgb(0 21 41 / 2%) 0 1px 4px;
|
||||
color: var(--theme-logo-text-color);
|
||||
font-size: 18px;
|
||||
.el-avatar {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
.logo-title {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="navbar-container">
|
||||
<Logo />
|
||||
<div class="navbar-left">代码生成器工作台</div>
|
||||
<div class="navbar-right">
|
||||
<a href="https://github.com/makunet/maku-generator" target="_blank">
|
||||
<svg-icon icon="icon-github-fill"></svg-icon>
|
||||
</a>
|
||||
<a href="https://gitee.com/makunet/maku-generator" target="_blank">
|
||||
<svg-icon icon="icon-gitee"></svg-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Logo from './components/Logo.vue'
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navbar-container {
|
||||
height: var(--theme-header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--theme-header-bg-color);
|
||||
border-bottom: 1px solid var(--theme-border-color-light);
|
||||
color: var(--theme-header-text-color);
|
||||
::v-deep(.svg-icon) {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
height: var(--theme-header-height);
|
||||
line-height: var(--theme-header-height);
|
||||
padding: 0 12px;
|
||||
svg {
|
||||
color: var(--theme-header-text-color) !important;
|
||||
font-size: 16px;
|
||||
}
|
||||
&:hover {
|
||||
background: var(--theme-header-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.navbar-left {
|
||||
flex: 1;
|
||||
height: inherit;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.navbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
&-link {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
&-photo {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, h } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
export default defineComponent({
|
||||
created() {
|
||||
const { params, query } = useRoute()
|
||||
const { path } = params
|
||||
const router = useRouter()
|
||||
router.replace({ path: '/' + path, query }).catch(err => {
|
||||
console.warn(err)
|
||||
})
|
||||
},
|
||||
render() {
|
||||
return h('div')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<el-scrollbar>
|
||||
<el-menu
|
||||
:default-active="defaultActive"
|
||||
background-color="transparent"
|
||||
:collapse-transition="false"
|
||||
mode="vertical"
|
||||
router
|
||||
>
|
||||
<menu-item v-for="menu in menuRoutes" :key="menu.path" :menu="menu"></menu-item>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MenuItem from './MenuItem.vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
import { menuRoutes } from '@/router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const defaultActive = computed(() => {
|
||||
const { path } = route
|
||||
return path
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<el-sub-menu v-if="menu.children?.length > 0" :key="menu.path" :index="menu.path">
|
||||
<template #title>
|
||||
<svg-icon :icon="menu.meta.icon"></svg-icon>
|
||||
<span>{{ menu.meta.title }}</span>
|
||||
</template>
|
||||
<menu-item v-for="sub in menu.children" :key="sub.path" :menu="sub"></menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item v-else :key="menu.path" :index="menu.path">
|
||||
<svg-icon :icon="menu.meta.icon"></svg-icon>
|
||||
<template #title>{{ menu.meta.title }}</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
|
||||
defineProps({
|
||||
menu: {
|
||||
type: Object as PropType<any>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<el-aside class="layout-sidebar">
|
||||
<Menu />
|
||||
</el-aside>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Menu from './components/Menu.vue'
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<el-container class="layout-container">
|
||||
<el-header class="layout-header">
|
||||
<Navbar />
|
||||
</el-header>
|
||||
<el-container>
|
||||
<Sidebar />
|
||||
<el-main class="layout-main">
|
||||
<el-scrollbar class="layout-scrollbar">
|
||||
<div class="layout-card">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<component :is="Component" :key="route.name" />
|
||||
</router-view>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router'
|
||||
import Sidebar from '@/layout/components/Sidebar/index.vue'
|
||||
import Navbar from '@/layout/components/Navbar/index.vue'
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createApp } from 'vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import App from './App.vue'
|
||||
import { router } from './router'
|
||||
import 'virtual:svg-icons-register'
|
||||
import 'xe-utils'
|
||||
import VXETable from 'vxe-table'
|
||||
import SvgIcon from '@/components/svg-icon'
|
||||
import 'vxe-table/lib/style.css'
|
||||
|
||||
import '@/icons/iconfont/iconfont'
|
||||
import 'element-plus/dist/index.css'
|
||||
import '@/styles/index.scss'
|
||||
|
||||
VXETable.setup({
|
||||
zIndex: 3000,
|
||||
select: {
|
||||
transfer: true
|
||||
}
|
||||
})
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
app.use(SvgIcon)
|
||||
app.use(ElementPlus)
|
||||
app.use(VXETable)
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,90 @@
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
|
||||
|
||||
export const menuRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/p/gen',
|
||||
meta: {
|
||||
title: '代码生成器',
|
||||
icon: 'icon-appstore'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/gen/generator',
|
||||
name: 'Generator',
|
||||
component: () => import('../views/generator/index.vue'),
|
||||
meta: {
|
||||
title: '代码生成',
|
||||
icon: 'icon-fire'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/gen/datasource',
|
||||
name: 'DataSource',
|
||||
component: () => import('../views/datasource/index.vue'),
|
||||
meta: {
|
||||
title: '数据源管理',
|
||||
icon: 'icon-database-fill'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/gen/field-type',
|
||||
name: 'FieldType',
|
||||
component: () => import('../views/field-type/index.vue'),
|
||||
meta: {
|
||||
title: '字段类型映射',
|
||||
icon: 'icon-menu'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/gen/base-class',
|
||||
name: 'BaseClass',
|
||||
component: () => import('../views/base-class/index.vue'),
|
||||
meta: {
|
||||
title: '基类管理',
|
||||
icon: 'icon-cluster'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/gen/project',
|
||||
name: 'ProjectIndex',
|
||||
component: () => import('../views/project/index.vue'),
|
||||
meta: {
|
||||
title: '项目名变更',
|
||||
icon: 'icon-edit-square'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
export const constantRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/redirect',
|
||||
component: () => import('../layout/index.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '/redirect/:path(.*)',
|
||||
component: () => import('../layout/components/Router/Redirect.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('../layout/index.vue'),
|
||||
redirect: '/gen/generator',
|
||||
children: [...menuRoutes]
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
component: () => import('../views/404.vue')
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)',
|
||||
redirect: '/404'
|
||||
}
|
||||
]
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: constantRoutes
|
||||
})
|
||||
@@ -0,0 +1,96 @@
|
||||
// cover some element-plus styles
|
||||
|
||||
.el-menu {
|
||||
border-right: none !important;
|
||||
&:not(.el-menu--collapse) {
|
||||
width: var(--theme-aside-width);
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
color: var(--theme-menu-text-color) !important;
|
||||
height: 48px !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.el-menu.el-menu--horizontal {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
height: 45px !important;
|
||||
line-height: 45px !important;
|
||||
}
|
||||
|
||||
.el-menu-item a,
|
||||
.el-menu-item a:hover,
|
||||
.el-menu-item i,
|
||||
.el-sub-menu__title i {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.el-menu-item .svg-icon,
|
||||
.el-sub-menu .svg-icon {
|
||||
font-size: 16px !important;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 5px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-sub-menu.is-active > :first-child {
|
||||
overflow: hidden;
|
||||
color: var(--theme-menu-hover-color) !important;
|
||||
}
|
||||
|
||||
.el-menu-item.is-active {
|
||||
border-right: 2px solid var(--theme-menu-border-color);
|
||||
position: relative;
|
||||
right: 1px;
|
||||
overflow: hidden;
|
||||
color: var(--theme-menu-hover-color) !important;
|
||||
background-color: var(--theme-menu-hover-bg-color) !important;
|
||||
&:hover {
|
||||
background-color: var(--theme-menu-hover-bg-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-sub-menu.is-active.is-opened {
|
||||
.el-sub-menu__title {
|
||||
background-color: unset !important;
|
||||
color: var(--theme-menu-hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标 hover 时
|
||||
.el-menu-item:hover,
|
||||
.el-sub-menu__title:hover {
|
||||
background-color: unset !important;
|
||||
color: var(--theme-menu-hover-color) !important;
|
||||
}
|
||||
|
||||
.el-pagination {
|
||||
margin-top: 15px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.el-form--inline .el-form-item {
|
||||
margin-right: 16px !important;
|
||||
}
|
||||
|
||||
.el-drawer .el-drawer__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--theme-header-height);
|
||||
padding: 0 15px;
|
||||
margin-bottom: 0 !important;
|
||||
font-size: 15px;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.el-card.is-always-shadow {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
@import './variables.scss';
|
||||
@import './layout.scss';
|
||||
@import './element.scss';
|
||||
@@ -0,0 +1,68 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
|
||||
'微软雅黑', Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a,
|
||||
a:focus,
|
||||
a:hover {
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.layout-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.layout-sidebar {
|
||||
background: var(--theme-menu-bg-color);
|
||||
border-right: var(--theme-border-color-light) 1px solid;
|
||||
height: inherit;
|
||||
width: var(--theme-aside-width) !important;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden !important;
|
||||
.el-scrollbar__view {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
.layout-header {
|
||||
padding: 0 !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: var(--theme-header-height);
|
||||
}
|
||||
.layout-main {
|
||||
padding: 0 !important;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
background-color: var(--theme-main-bg-color);
|
||||
.layout-card {
|
||||
min-height: calc(100vh - 30px - var(--theme-header-height));
|
||||
}
|
||||
}
|
||||
.layout-scrollbar {
|
||||
height: calc(100vh - 30px - var(--theme-header-height));
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
:root {
|
||||
--el-color-primary: #1e97f8;
|
||||
|
||||
--theme-aside-width: 200px;
|
||||
--theme-header-height: 50px;
|
||||
--theme-border-color-light: #f1f2f3;
|
||||
--theme-logo-text-color: #ffffff;
|
||||
|
||||
--theme-menu-text-color: #333333;
|
||||
--theme-menu-bg-color: #ffffff;
|
||||
--theme-menu-border-color: var(--el-color-primary);
|
||||
--theme-menu-hover-color: var(--el-color-primary);
|
||||
--theme-menu-hover-bg-color: var(--el-color-primary-light-9);
|
||||
|
||||
--theme-header-bg-color: var(--el-color-primary);
|
||||
--theme-header-text-color: #ffffff;
|
||||
--theme-header-hover-color: rgba(0, 0, 0, 0.1);
|
||||
|
||||
--theme-main-bg-color: #f0f2f5;
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
declare module '*.svg'
|
||||
declare module '*.png'
|
||||
declare module '*.jpg'
|
||||
declare module '*.gif'
|
||||
declare module '*.scss'
|
||||
declare module '*.ts'
|
||||
declare module '*.js'
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import axios from 'axios'
|
||||
import qs from 'qs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getToken } from './token'
|
||||
|
||||
// axios实例
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.VITE_BASE_API as any,
|
||||
timeout: 60000,
|
||||
headers: { 'Content-Type': 'application/json;charset=UTF-8' }
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
service.interceptors.request.use(
|
||||
(config: any) => {
|
||||
// 追加时间戳,防止GET请求缓存
|
||||
if (config.method?.toUpperCase() === 'GET') {
|
||||
config.params = { ...config.params, t: new Date().getTime() }
|
||||
}
|
||||
|
||||
config.headers.token = config.headers.token || getToken()
|
||||
|
||||
if (Object.values(config.headers).includes('application/x-www-form-urlencoded')) {
|
||||
config.data = qs.stringify(config.data)
|
||||
}
|
||||
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
if (response.status !== 200) {
|
||||
return Promise.reject(new Error(response.statusText || 'Error'))
|
||||
}
|
||||
|
||||
const res = response.data
|
||||
// 响应成功
|
||||
if (res.code === 200) {
|
||||
return res
|
||||
}
|
||||
|
||||
// 错误提示
|
||||
ElMessage.error(res.msg)
|
||||
|
||||
return Promise.reject(new Error(res.msg || 'Error'))
|
||||
},
|
||||
error => {
|
||||
ElMessage.error(error.message)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 导出 axios 实例
|
||||
export default service
|
||||
@@ -0,0 +1,30 @@
|
||||
const TOKEN_CODE = 'access_token'
|
||||
|
||||
export function getToken() {
|
||||
return 'dev'
|
||||
return localStorage.getItem(TOKEN_CODE)
|
||||
}
|
||||
|
||||
export function setToken(token) {
|
||||
localStorage.setItem(TOKEN_CODE, token)
|
||||
}
|
||||
|
||||
export function removeToken() {
|
||||
localStorage.removeItem(TOKEN_CODE)
|
||||
}
|
||||
|
||||
// export async function refreshAccessToken() {
|
||||
// const tokenItem = lStorage.getItem(TOKEN_CODE)
|
||||
// if (!tokenItem) {
|
||||
// return
|
||||
// }
|
||||
// const { time } = tokenItem
|
||||
// // token生成或者刷新后30分钟内不执行刷新
|
||||
// if (new Date().getTime() - time <= 1000 * 60 * 30) return
|
||||
// try {
|
||||
// const res = await api.refreshToken()
|
||||
// setToken(res.data.token)
|
||||
// } catch (error) {
|
||||
// console.error(error)
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { App, Plugin } from 'vue'
|
||||
import { AES, lib, enc, mode, pad } from 'crypto-js'
|
||||
// 全局组件安装
|
||||
export const withInstall = <T>(component: T, alias?: string) => {
|
||||
const comp = component as any
|
||||
comp.install = (app: App) => {
|
||||
app.component(comp.__name || comp.displayName, component)
|
||||
if (alias) {
|
||||
app.config.globalProperties[alias] = component
|
||||
}
|
||||
}
|
||||
return component as T & Plugin
|
||||
}
|
||||
|
||||
// 密钥
|
||||
const ENCRYPT_KEY = 'makulowcodeyyds1'
|
||||
|
||||
export const decrypt = (ciphertext: string): string => {
|
||||
// 将密文转换为CipherParams对象
|
||||
const cipherParams = lib.CipherParams.create({
|
||||
ciphertext: enc.Base64.parse(ciphertext)
|
||||
})
|
||||
|
||||
// 使用密钥解密CipherParams对象
|
||||
const decrypted = AES.decrypt(cipherParams, enc.Utf8.parse(ENCRYPT_KEY), {
|
||||
mode: mode.ECB,
|
||||
padding: pad.Pkcs7
|
||||
})
|
||||
|
||||
// 获取明文
|
||||
return decrypted.toString(enc.Utf8)
|
||||
}
|
||||
|
||||
export const encrypt = (plaintext: string): string => {
|
||||
// 将明文转换为要加密的格式
|
||||
const message = enc.Utf8.parse(plaintext)
|
||||
|
||||
// 使用密钥加密明文
|
||||
const encrypted = AES.encrypt(message, enc.Utf8.parse(ENCRYPT_KEY), {
|
||||
mode: mode.ECB,
|
||||
padding: pad.Pkcs7
|
||||
})
|
||||
|
||||
return encrypted.toString()
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="layout-error">
|
||||
<img src="@/assets/404.png" alt="404" />
|
||||
<div>
|
||||
<el-button type="primary" @click="onBack">返回</el-button>
|
||||
<el-button type="success" @click="onToHome">主页</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
|
||||
const onBack = () => {
|
||||
router.back()
|
||||
}
|
||||
const onToHome = () => {
|
||||
router.replace('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout-error {
|
||||
img {
|
||||
width: 800px;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="!dataForm.id ? '新增' : '修改'"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="dataForm"
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
@keyup.enter="submitHandle()"
|
||||
>
|
||||
<el-form-item label="基类编码" prop="code">
|
||||
<el-input v-model="dataForm.code" placeholder="基类编码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="基类包名" prop="packageName">
|
||||
<el-input v-model="dataForm.packageName" placeholder="基类包名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="基类字段" prop="fields">
|
||||
<el-input
|
||||
v-model="dataForm.fields"
|
||||
placeholder="基类字段,多个字段,用英文逗号分隔"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="dataForm.remark" placeholder="备注"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitHandle()">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus/es'
|
||||
import { useBaseClassApi, useBaseClassSubmitApi } from '@/api/baseClass'
|
||||
|
||||
const emit = defineEmits(['refreshDataList'])
|
||||
|
||||
const visible = ref(false)
|
||||
const dataFormRef = ref()
|
||||
|
||||
const dataForm = reactive({
|
||||
id: '',
|
||||
packageName: '',
|
||||
code: '',
|
||||
fields: '',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const init = (id?: number) => {
|
||||
visible.value = true
|
||||
dataForm.id = ''
|
||||
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
// id 存在则为修改
|
||||
if (id) {
|
||||
getBaseClass(id)
|
||||
}
|
||||
}
|
||||
|
||||
const getBaseClass = (id: number) => {
|
||||
useBaseClassApi(id).then(res => {
|
||||
Object.assign(dataForm, res.data)
|
||||
})
|
||||
}
|
||||
|
||||
const dataRules = ref({
|
||||
packageName: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
fields: [{ required: true, message: '必填项不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 表单提交
|
||||
const submitHandle = () => {
|
||||
dataFormRef.value.validate((valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false
|
||||
}
|
||||
|
||||
useBaseClassSubmitApi(dataForm).then(() => {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
visible.value = false
|
||||
emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList()">
|
||||
<el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="state.queryForm.code" placeholder="基类编码"></el-input>
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="danger" @click="deleteBatchHandle()">删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
v-loading="state.dataListLoading"
|
||||
:data="state.dataList"
|
||||
border
|
||||
style="width: 100%"
|
||||
@selection-change="selectionChangeHandle"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="50"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="code"
|
||||
label="基类编码"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="packageName"
|
||||
label="基类包名"
|
||||
show-overflow-tooltip
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="fields"
|
||||
label="基类字段"
|
||||
show-overflow-tooltip
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
show-overflow-tooltip
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" header-align="center" align="center" width="150">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="addOrUpdateHandle(scope.row.id)">编辑</el-button>
|
||||
<el-button type="primary" link @click="deleteBatchHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
:current-page="state.page"
|
||||
:page-sizes="state.pageSizes"
|
||||
:page-size="state.limit"
|
||||
:total="state.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
>
|
||||
</el-pagination>
|
||||
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update ref="addOrUpdateRef" @refresh-data-list="getDataList"></add-or-update>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { IHooksOptions } from '@/hooks/interface'
|
||||
import { useCrud } from '@/hooks'
|
||||
import AddOrUpdate from './add-or-update.vue'
|
||||
|
||||
const state: IHooksOptions = reactive({
|
||||
dataListUrl: '/gen/baseclass/page',
|
||||
deleteUrl: '/gen/baseclass',
|
||||
queryForm: {
|
||||
code: ''
|
||||
}
|
||||
})
|
||||
|
||||
const addOrUpdateRef = ref()
|
||||
const addOrUpdateHandle = (id?: number) => {
|
||||
addOrUpdateRef.value.init(id)
|
||||
}
|
||||
|
||||
const {
|
||||
getDataList,
|
||||
selectionChangeHandle,
|
||||
sizeChangeHandle,
|
||||
currentChangeHandle,
|
||||
deleteBatchHandle
|
||||
} = useCrud(state)
|
||||
</script>
|
||||
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="!dataForm.id ? '新增' : '修改'"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="dataForm"
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
@keyup.enter="submitHandle()"
|
||||
>
|
||||
<el-form-item label="连接名" prop="connName">
|
||||
<el-input v-model="dataForm.connName" placeholder="连接名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="dbType" label="数据库类型">
|
||||
<el-select v-model="dataForm.dbType" clearable placeholder="数据库类型" style="width: 100%">
|
||||
<el-option value="MySQL" label="MySQL"></el-option>
|
||||
<el-option value="Oracle" label="Oracle"></el-option>
|
||||
<el-option value="PostgreSQL" label="PostgreSQL"></el-option>
|
||||
<el-option value="SQLServer" label="SQLServer"></el-option>
|
||||
<el-option value="DM" label="达梦8"></el-option>
|
||||
<el-option value="KingBase" label="人大金仓"></el-option>
|
||||
<el-option value="Clickhouse" label="Clickhouse"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库URL" prop="connUrl">
|
||||
<el-input v-model="dataForm.connUrl" placeholder="数据库URL"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="dataForm.username" placeholder="用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input
|
||||
v-model="dataForm.password"
|
||||
autocomplete="off"
|
||||
type="password"
|
||||
placeholder="密码"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitHandle()">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus/es'
|
||||
import { useDataSourceApi, useDataSourceSubmitApi } from '@/api/datasource'
|
||||
import { decrypt, encrypt } from '@/utils/tool'
|
||||
|
||||
const emit = defineEmits(['refreshDataList'])
|
||||
|
||||
const visible = ref(false)
|
||||
const dataFormRef = ref()
|
||||
|
||||
const dataForm = reactive({
|
||||
id: '',
|
||||
dbType: '',
|
||||
connName: '',
|
||||
connUrl: '',
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
const init = (id?: number) => {
|
||||
visible.value = true
|
||||
dataForm.id = ''
|
||||
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
// id 存在则为修改
|
||||
if (id) {
|
||||
getDataSource(id)
|
||||
}
|
||||
}
|
||||
|
||||
const getDataSource = (id: number) => {
|
||||
useDataSourceApi(id).then(res => {
|
||||
res.data.password = decrypt(res.data.password)
|
||||
Object.assign(dataForm, res.data)
|
||||
})
|
||||
}
|
||||
|
||||
const dataRules = ref({
|
||||
dbType: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
connName: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
connUrl: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
username: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '必填项不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 表单提交
|
||||
const submitHandle = () => {
|
||||
dataFormRef.value.validate((valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false
|
||||
}
|
||||
|
||||
useDataSourceSubmitApi({ ...dataForm, password: encrypt(dataForm.password) }).then(() => {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
visible.value = false
|
||||
emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="state.queryForm.connName" placeholder="连接名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="dbType">
|
||||
<el-select v-model="state.queryForm.dbType" clearable placeholder="数据库类型">
|
||||
<el-option value="MySQL" label="MySQL"></el-option>
|
||||
<el-option value="Oracle" label="Oracle"></el-option>
|
||||
<el-option value="PostgreSQL" label="PostgreSQL"></el-option>
|
||||
<el-option value="SQLServer" label="SQLServer"></el-option>
|
||||
<el-option value="DM" label="达梦8"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="danger" @click="deleteBatchHandle()">删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
v-loading="state.dataListLoading"
|
||||
:data="state.dataList"
|
||||
border
|
||||
style="width: 100%"
|
||||
@selection-change="selectionChangeHandle"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="50"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="connName"
|
||||
label="连接名"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="dbType"
|
||||
label="数据库类型"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="connUrl"
|
||||
label="数据库URL"
|
||||
show-overflow-tooltip
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="username"
|
||||
label="用户名"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column label="密码" header-align="center" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ formatPassword(scope.row.password) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" header-align="center" align="center" width="170">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="datasourceHandle(scope.row.id)">测试</el-button>
|
||||
<el-button type="primary" link @click="addOrUpdateHandle(scope.row.id)">编辑</el-button>
|
||||
<el-button type="primary" link @click="deleteBatchHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
:current-page="state.page"
|
||||
:page-sizes="state.pageSizes"
|
||||
:page-size="state.limit"
|
||||
:total="state.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
>
|
||||
</el-pagination>
|
||||
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update ref="addOrUpdateRef" @refresh-data-list="getDataList"></add-or-update>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { IHooksOptions } from '@/hooks/interface'
|
||||
import { useDataSourceTestApi } from '@/api/datasource'
|
||||
import { useCrud } from '@/hooks'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import AddOrUpdate from './add-or-update.vue'
|
||||
import { decrypt } from '@/utils/tool'
|
||||
|
||||
const state: IHooksOptions = reactive({
|
||||
dataListUrl: '/gen/datasource/page',
|
||||
deleteUrl: '/gen/datasource',
|
||||
queryForm: {
|
||||
connName: '',
|
||||
dbType: ''
|
||||
}
|
||||
})
|
||||
|
||||
const datasourceHandle = (id: number) => {
|
||||
useDataSourceTestApi(id).then((res: any) => {
|
||||
ElMessage.success(res.data)
|
||||
})
|
||||
}
|
||||
|
||||
const addOrUpdateRef = ref()
|
||||
const addOrUpdateHandle = (id?: number) => {
|
||||
addOrUpdateRef.value.init(id)
|
||||
}
|
||||
|
||||
const {
|
||||
getDataList,
|
||||
selectionChangeHandle,
|
||||
sizeChangeHandle,
|
||||
currentChangeHandle,
|
||||
deleteBatchHandle
|
||||
} = useCrud(state)
|
||||
|
||||
const formatPassword = (str: string): string => {
|
||||
return Array(decrypt(str).length).fill('•').join('')
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="!dataForm.id ? '新增' : '修改'"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="dataForm"
|
||||
:rules="dataRules"
|
||||
label-width="120px"
|
||||
@keyup.enter="submitHandle()"
|
||||
>
|
||||
<el-form-item label="字段类型" prop="columnType">
|
||||
<el-input v-model="dataForm.columnType" placeholder="字段类型"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="属性类型" prop="attrType">
|
||||
<el-input v-model="dataForm.attrType" placeholder="属性类型"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="属性包名" prop="packageName">
|
||||
<el-input v-model="dataForm.packageName" placeholder="属性包名"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitHandle()">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus/es'
|
||||
import { useFieldTypeApi, useFieldTypeSubmitApi } from '@/api/fieldType'
|
||||
|
||||
const emit = defineEmits(['refreshDataList'])
|
||||
|
||||
const visible = ref(false)
|
||||
const dataFormRef = ref()
|
||||
|
||||
const dataForm = reactive({
|
||||
id: '',
|
||||
columnType: '',
|
||||
attrType: '',
|
||||
packageName: '',
|
||||
createDate: ''
|
||||
})
|
||||
|
||||
const init = (id?: number) => {
|
||||
visible.value = true
|
||||
dataForm.id = ''
|
||||
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
// id 存在则为修改
|
||||
if (id) {
|
||||
getFieldType(id)
|
||||
}
|
||||
}
|
||||
|
||||
const getFieldType = (id: number) => {
|
||||
useFieldTypeApi(id).then(res => {
|
||||
Object.assign(dataForm, res.data)
|
||||
})
|
||||
}
|
||||
|
||||
const dataRules = ref({
|
||||
columnType: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
attrType: [{ required: true, message: '必填项不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 表单提交
|
||||
const submitHandle = () => {
|
||||
dataFormRef.value.validate((valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false
|
||||
}
|
||||
|
||||
useFieldTypeSubmitApi(dataForm).then(() => {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
visible.value = false
|
||||
emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList()">
|
||||
<el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="state.queryForm.columnType" placeholder="字段类型"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="state.queryForm.attrType" placeholder="属性类型"></el-input>
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="danger" @click="deleteBatchHandle()">删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
v-loading="state.dataListLoading"
|
||||
:data="state.dataList"
|
||||
border
|
||||
style="width: 100%"
|
||||
@selection-change="selectionChangeHandle"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="50"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="columnType"
|
||||
label="字段类型"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="attrType"
|
||||
label="属性类型"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="packageName"
|
||||
label="属性包名"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" header-align="center" align="center" width="150">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="addOrUpdateHandle(scope.row.id)">编辑</el-button>
|
||||
<el-button type="primary" link @click="deleteBatchHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
:current-page="state.page"
|
||||
:page-sizes="state.pageSizes"
|
||||
:page-size="state.limit"
|
||||
:total="state.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
>
|
||||
</el-pagination>
|
||||
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update ref="addOrUpdateRef" @refresh-data-list="getDataList"></add-or-update>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { IHooksOptions } from '@/hooks/interface'
|
||||
import { useCrud } from '@/hooks'
|
||||
import AddOrUpdate from './add-or-update.vue'
|
||||
|
||||
const state: IHooksOptions = reactive({
|
||||
dataListUrl: '/gen/fieldtype/page',
|
||||
deleteUrl: '/gen/fieldtype',
|
||||
queryForm: {
|
||||
columnType: '',
|
||||
attrType: '',
|
||||
packageName: ''
|
||||
}
|
||||
})
|
||||
|
||||
const addOrUpdateRef = ref()
|
||||
const addOrUpdateHandle = (id?: number) => {
|
||||
addOrUpdateRef.value.init(id)
|
||||
}
|
||||
|
||||
const {
|
||||
getDataList,
|
||||
selectionChangeHandle,
|
||||
sizeChangeHandle,
|
||||
currentChangeHandle,
|
||||
deleteBatchHandle
|
||||
} = useCrud(state)
|
||||
</script>
|
||||
@@ -0,0 +1,330 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" title="编辑" :size="1200" :with-header="false">
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane label="属性设置" name="field">
|
||||
<vxe-table
|
||||
ref="fieldTable"
|
||||
border
|
||||
row-key
|
||||
class="sortable-row-gen"
|
||||
:data="fieldList"
|
||||
:checkbox-config="{ checkStrictly: true }"
|
||||
:edit-config="{ trigger: 'click', mode: 'cell' }"
|
||||
>
|
||||
<vxe-column type="seq" width="60"></vxe-column>
|
||||
<vxe-column width="60" title="拖动">
|
||||
<template #default>
|
||||
<span class="drag-btn">
|
||||
<i class="vxe-icon-sort"></i>
|
||||
</span>
|
||||
</template>
|
||||
<template #header>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
content="按住后可以上下拖动排序"
|
||||
placement="top-start"
|
||||
>
|
||||
<i class="vxe-icon-question-circle-fill"></i>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="name" title="字段名"></vxe-column>
|
||||
<vxe-column
|
||||
field="description"
|
||||
title="说明"
|
||||
:edit-render="{ name: 'input' }"
|
||||
></vxe-column>
|
||||
<vxe-column field="type" title="字段类型"></vxe-column>
|
||||
<!-- <vxe-column field="name" title="属性名" :edit-render="{ name: 'input' }"></vxe-column>
|
||||
<vxe-column field="attrType" title="属性类型">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.attrType">
|
||||
<el-option
|
||||
v-for="item in typeList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</vxe-column> -->
|
||||
<vxe-column field="autoFill" title="自动填充">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.autoFill">
|
||||
<el-option
|
||||
v-for="item in fillList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="primary_key" title="主键">
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.primary_key"></el-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="表单配置" name="form">
|
||||
<vxe-table
|
||||
ref="formTable"
|
||||
border
|
||||
row-key
|
||||
:data="fieldList"
|
||||
:checkbox-config="{ checkStrictly: true }"
|
||||
:edit-config="{ trigger: 'click', mode: 'cell' }"
|
||||
>
|
||||
<vxe-column field="name" title="属性名"></vxe-column>
|
||||
<vxe-column field="description" title="说明"></vxe-column>
|
||||
<vxe-column field="editable" title="表单显示">
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.editable"></el-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="required" title="表单必填">
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.required"></el-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="validations" title="表单效验" :edit-render="{ name: 'input' }">
|
||||
<template #default="{ row }">
|
||||
{{ JSON.stringify(row.validations) }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="display_type" title="表单类型">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.display_type">
|
||||
<el-option
|
||||
v-for="item in display_typeList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column
|
||||
field="formDict"
|
||||
title="表单字典类型"
|
||||
:edit-render="{ name: 'input' }"
|
||||
></vxe-column>
|
||||
</vxe-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="列表配置" name="grid">
|
||||
<vxe-table
|
||||
ref="gridTable"
|
||||
border
|
||||
row-key
|
||||
:data="fieldList"
|
||||
:checkbox-config="{ checkStrictly: true }"
|
||||
:edit-config="{ trigger: 'click', mode: 'cell' }"
|
||||
>
|
||||
<vxe-column field="name" title="属性名"></vxe-column>
|
||||
<vxe-column field="description" title="说明"></vxe-column>
|
||||
<vxe-column field="listable" title="列表显示">
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.listable"></el-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="sortable" title="列表排序">
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.sortable"></el-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="查询配置" name="query">
|
||||
<vxe-table
|
||||
ref="queryTable"
|
||||
border
|
||||
row-key
|
||||
:data="fieldList"
|
||||
:checkbox-config="{ checkStrictly: true }"
|
||||
:edit-config="{ trigger: 'click', mode: 'cell' }"
|
||||
>
|
||||
<vxe-column field="name" title="属性名"></vxe-column>
|
||||
<vxe-column field="description" title="说明"></vxe-column>
|
||||
<vxe-column field="filterable" title="查询显示">
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.filterable"></el-checkbox>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="filter_operator" title="查询方式">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.filter_operator">
|
||||
<el-option
|
||||
v-for="item in queryList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="display_type" title="查询表单类型">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.display_type">
|
||||
<el-option
|
||||
v-for="item in display_typeList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitHandle()">确定</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, reactive, ref } from 'vue'
|
||||
import { ElMessage, TabsPaneContext } from 'element-plus/es'
|
||||
import Sortable from 'sortablejs'
|
||||
import { useTableFieldSubmitApi } from '@/api/table'
|
||||
import { useTableApi } from '@/api/table'
|
||||
import { useFieldTypeListApi } from '@/api/fieldType'
|
||||
import { VxeTableInstance } from 'vxe-table'
|
||||
|
||||
const activeName = ref()
|
||||
const fieldTable = ref<VxeTableInstance>()
|
||||
const formTable = ref<VxeTableInstance>()
|
||||
const gridTable = ref<VxeTableInstance>()
|
||||
const queryTable = ref<VxeTableInstance>()
|
||||
|
||||
const handleClick = (tab: TabsPaneContext) => {
|
||||
if (tab.paneName !== 'field') {
|
||||
formTable.value?.loadData(fieldList.value)
|
||||
gridTable.value?.loadData(fieldList.value)
|
||||
queryTable.value?.loadData(fieldList.value)
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits(['refreshDataList'])
|
||||
const visible = ref(false)
|
||||
const dataFormRef = ref()
|
||||
|
||||
const sortable = ref() as any
|
||||
|
||||
const typeList = ref([]) as any
|
||||
const tableId = ref()
|
||||
const fieldList = ref([])
|
||||
const fillList = reactive([
|
||||
{ label: 'DEFAULT', value: 'DEFAULT' },
|
||||
{ label: 'INSERT', value: 'INSERT' },
|
||||
{ label: 'UPDATE', value: 'UPDATE' },
|
||||
{ label: 'INSERT_UPDATE', value: 'INSERT_UPDATE' }
|
||||
])
|
||||
|
||||
const queryList = reactive([
|
||||
{ label: '=', value: '=' },
|
||||
{ label: '!=', value: '!=' },
|
||||
{ label: '>', value: '>' },
|
||||
{ label: '>=', value: '>=' },
|
||||
{ label: '<', value: '<' },
|
||||
{ label: '<=', value: '<=' },
|
||||
{ label: 'like', value: 'like' },
|
||||
{ label: 'left like', value: 'left like' },
|
||||
{ label: 'right like', value: 'right like' }
|
||||
])
|
||||
|
||||
const display_typeList = reactive([
|
||||
{ label: '单行文本', value: 'text' },
|
||||
{ label: '多行文本', value: 'textarea' },
|
||||
{ label: '富文本编辑器', value: 'editor' },
|
||||
{ label: '下拉框', value: 'select' },
|
||||
{ label: '单选按钮', value: 'radio' },
|
||||
{ label: '复选框', value: 'checkbox' },
|
||||
{ label: '日期', value: 'date' },
|
||||
{ label: '日期时间', value: 'datetime' }
|
||||
])
|
||||
|
||||
const init = (id: number) => {
|
||||
visible.value = true
|
||||
tableId.value = id
|
||||
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
activeName.value = 'field'
|
||||
|
||||
rowDrop()
|
||||
getTable(id)
|
||||
getFieldTypeList()
|
||||
}
|
||||
|
||||
const rowDrop = () => {
|
||||
nextTick(() => {
|
||||
const el: any = window.document.querySelector('.body--wrapper>.vxe-table--body tbody')
|
||||
sortable.value = Sortable.create(el, {
|
||||
handle: '.drag-btn',
|
||||
onEnd: (e: any) => {
|
||||
const { newIndex, oldIndex } = e
|
||||
const currRow = fieldList.value.splice(oldIndex, 1)[0]
|
||||
fieldList.value.splice(newIndex, 0, currRow)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getTable = (id: number) => {
|
||||
useTableApi(id).then(res => {
|
||||
fieldList.value = res.data.fields.map(item => {
|
||||
item.autoFill = item.autoFill || 'DEFAULT'
|
||||
return item
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getFieldTypeList = async () => {
|
||||
typeList.value = []
|
||||
|
||||
// 获取数据
|
||||
const { data } = await useFieldTypeListApi()
|
||||
// 设置属性类型值
|
||||
data.forEach((item: any) => typeList.value.push({ label: item, value: item }))
|
||||
// 增加Object类型
|
||||
typeList.value.push({ label: 'Object', value: 'Object' })
|
||||
}
|
||||
|
||||
// 表单提交
|
||||
const submitHandle = () => {
|
||||
useTableFieldSubmitApi(tableId.value, { fields: fieldList.value }).then(() => {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
visible.value = false
|
||||
emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sortable-row-gen .drag-btn {
|
||||
cursor: move;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sortable-row-gen .vxe-body--row.sortable-ghost,
|
||||
.sortable-row-gen .vxe-body--row.sortable-chosen {
|
||||
background-color: #dfecfb;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="生成代码" :close-on-click-modal="false" draggable>
|
||||
<el-form ref="dataFormRef" :model="dataForm" :rules="dataRules" label-width="120px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表名" prop="tableName">
|
||||
<el-input v-model="dataForm.tableName" disabled placeholder="表名"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="说明" prop="tableComment">
|
||||
<el-input v-model="dataForm.tableComment" placeholder="说明"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="类名" prop="className">
|
||||
<el-input v-model="dataForm.className" placeholder="类名"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="baseclassId" label="继承">
|
||||
<el-select
|
||||
v-model="dataForm.baseclassId"
|
||||
placeholder="继承"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in baseClassList"
|
||||
:key="item.id"
|
||||
:label="item.code"
|
||||
:value="item.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="模块名" prop="moduleName">
|
||||
<el-input v-model="dataForm.moduleName" placeholder="模块名"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="功能名" prop="functionName">
|
||||
<el-input v-model="dataForm.functionName" placeholder="功能名"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目包名" prop="packageName">
|
||||
<el-input v-model="dataForm.packageName" placeholder="项目包名"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="版本号" prop="version">
|
||||
<el-input v-model="dataForm.version" placeholder="版本号"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="默认作者" prop="author">
|
||||
<el-input v-model="dataForm.author" placeholder="默认作者"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="作者邮箱" prop="email">
|
||||
<el-input v-model="dataForm.email" placeholder="作者邮箱"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="生成方式" prop="generatorType">
|
||||
<el-radio-group v-model="dataForm.generatorType">
|
||||
<el-radio :label="0">zip压缩包</el-radio>
|
||||
<el-radio :label="1">自定义路径</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表单布局" prop="formLayout">
|
||||
<el-radio-group v-model="dataForm.formLayout">
|
||||
<el-radio :label="1">一列</el-radio>
|
||||
<el-radio :label="2">两列</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item v-if="dataForm.generatorType === 1" label="后端生成路径" prop="backendPath">
|
||||
<el-input v-model="dataForm.backendPath" placeholder="后端生成路径"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dataForm.generatorType === 1" label="前端生成路径" prop="frontendPath">
|
||||
<el-input v-model="dataForm.frontendPath" placeholder="前端生成路径"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitHandle()">保存</el-button>
|
||||
<el-button type="danger" @click="generatorHandle()">生成代码</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus/es'
|
||||
import { useBaseClassListApi } from '@/api/baseClass'
|
||||
import { useGeneratorApi, useDownloadApi } from '@/api/generator'
|
||||
import { useTableApi, useTableSubmitApi } from '@/api/table'
|
||||
|
||||
const emit = defineEmits(['refreshDataList'])
|
||||
|
||||
const visible = ref(false)
|
||||
const dataFormRef = ref()
|
||||
const baseClassList = ref<any[]>([])
|
||||
const dataForm = reactive({
|
||||
id: '',
|
||||
baseclassId: '',
|
||||
generatorType: 0,
|
||||
formLayout: 1,
|
||||
backendPath: '',
|
||||
frontendPath: '',
|
||||
packageName: '',
|
||||
email: '',
|
||||
author: '',
|
||||
version: '',
|
||||
moduleName: '',
|
||||
functionName: '',
|
||||
className: '',
|
||||
tableComment: '',
|
||||
tableName: ''
|
||||
})
|
||||
|
||||
const init = (id: number) => {
|
||||
visible.value = true
|
||||
dataForm.id = ''
|
||||
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
getBaseClassList()
|
||||
getTable(id)
|
||||
}
|
||||
|
||||
const getBaseClassList = () => {
|
||||
useBaseClassListApi().then(res => {
|
||||
baseClassList.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const getTable = (id: number) => {
|
||||
useTableApi(id).then(res => {
|
||||
Object.assign(dataForm, res.data)
|
||||
})
|
||||
}
|
||||
|
||||
const dataRules = ref({
|
||||
tableName: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
tableComment: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
className: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
packageName: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
author: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
moduleName: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
functionName: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
generatorType: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
formLayout: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
backendPath: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
frontendPath: [{ required: true, message: '必填项不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 保存
|
||||
const submitHandle = () => {
|
||||
dataFormRef.value.validate((valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false
|
||||
}
|
||||
|
||||
useTableSubmitApi(dataForm).then(() => {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
visible.value = false
|
||||
emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 生成代码
|
||||
const generatorHandle = () => {
|
||||
dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 先保存
|
||||
await useTableSubmitApi(dataForm)
|
||||
|
||||
// 生成代码,zip压缩包
|
||||
if (dataForm.generatorType === 0) {
|
||||
useDownloadApi([dataForm.id])
|
||||
visible.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 生成代码,自定义路径
|
||||
useGeneratorApi([dataForm.id]).then(() => {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
visible.value = false
|
||||
emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.generator-code .el-dialog__body {
|
||||
padding: 15px 30px 0 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="导入数据库表" :close-on-click-modal="false" draggable>
|
||||
<el-form ref="dataFormRef" :model="dataForm">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="数据源" prop="datasourceId">
|
||||
<el-select
|
||||
v-model="dataForm.datasourceId"
|
||||
style="width: 100%"
|
||||
placeholder="请选择数据源"
|
||||
@change="getTableList"
|
||||
>
|
||||
<el-option label="默认数据源" value="0"></el-option>
|
||||
<el-option
|
||||
v-for="ds in dataForm.datasourceList"
|
||||
:key="ds.id"
|
||||
:label="ds.connName"
|
||||
:value="ds.id"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="表名" prop="tableName" :label-width="80">
|
||||
<!-- 监听tableName值,如果发生变化,则调用getTableList -->
|
||||
<el-input
|
||||
v-model="dataForm.tableName"
|
||||
placeholder="请输入表名"
|
||||
style="width: 100%"
|
||||
@input="getTableList"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="dataForm.tableList"
|
||||
border
|
||||
style="width: 100%"
|
||||
:max-height="400"
|
||||
@selection-change="selectionChangeHandle"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="60"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="tableName"
|
||||
label="表名"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="tableComment"
|
||||
label="表说明"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitHandle()">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus/es'
|
||||
import { useDataSourceListApi } from '@/api/datasource'
|
||||
import { useTableImportSubmitApi } from '@/api/table'
|
||||
import { useDataSourceTableListApi } from '@/api/datasource'
|
||||
|
||||
const emit = defineEmits(['refreshDataList'])
|
||||
|
||||
const visible = ref(false)
|
||||
const dataFormRef = ref()
|
||||
|
||||
const dataForm = reactive({
|
||||
id: '',
|
||||
tableNameListSelections: [] as any,
|
||||
datasourceId: '',
|
||||
tableName: '',
|
||||
datasourceList: [] as any,
|
||||
tableList: [] as any
|
||||
})
|
||||
|
||||
// 多选
|
||||
const selectionChangeHandle = (selections: any[]) => {
|
||||
dataForm.tableNameListSelections = selections.map((item: any) => item['tableName'])
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
visible.value = true
|
||||
dataForm.id = ''
|
||||
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
dataForm.tableList = []
|
||||
|
||||
getDataSourceList()
|
||||
}
|
||||
|
||||
const getDataSourceList = () => {
|
||||
useDataSourceListApi().then(res => {
|
||||
dataForm.datasourceList = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const getTableList = () => {
|
||||
useDataSourceTableListApi(dataForm.datasourceId, dataForm.tableName).then(res => {
|
||||
dataForm.tableList = res.data
|
||||
})
|
||||
}
|
||||
|
||||
// 表单提交
|
||||
const submitHandle = () => {
|
||||
const tableNameList = dataForm.tableNameListSelections ? dataForm.tableNameListSelections : []
|
||||
if (tableNameList.length === 0) {
|
||||
ElMessage.warning('请选择记录')
|
||||
return
|
||||
}
|
||||
|
||||
useTableImportSubmitApi(dataForm.datasourceId, { table: tableNameList }).then(() => {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
visible.value = false
|
||||
emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="state.queryForm.name" placeholder="表名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="importHandle()">导入</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="success" @click="downloadBatchHandle()">生成代码</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="danger" @click="deleteBatchHandle()">删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
v-loading="state.dataListLoading"
|
||||
:data="state.dataList"
|
||||
border
|
||||
style="width: 100%"
|
||||
@selection-change="selectionChangeHandle"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="50"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="表名"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
label="表说明"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column prop="template" label="模板名" header-align="center" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.template">
|
||||
<el-option
|
||||
v-for="item in templateList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" header-align="center" align="center" width="300">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="editHandle(scope.row.id)">编辑</el-button>
|
||||
<el-button type="primary" link @click="previewHandle(scope.row.id)">预览</el-button>
|
||||
<el-button type="primary" link @click="generatorHandle(scope.row.id)">生成代码</el-button>
|
||||
<el-button type="primary" link @click="deleteBatchHandle(scope.row.id)">删除</el-button>
|
||||
<el-button type="primary" link @click="syncHandle(scope.row)">同步</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
:current-page="state.page"
|
||||
:page-sizes="state.pageSizes"
|
||||
:page-size="state.limit"
|
||||
:total="state.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
>
|
||||
</el-pagination>
|
||||
|
||||
<import ref="importRef" @refresh-data-list="getDataList"></import>
|
||||
<preview ref="previewRef" @refresh-data-list="getDataList"></preview>
|
||||
<edit ref="editRef" @refresh-data-list="getDataList"></edit>
|
||||
<generator ref="generatorRef" @refresh-data-list="getDataList"></generator>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { IHooksOptions } from '@/hooks/interface'
|
||||
import { useCrud } from '@/hooks'
|
||||
import Import from './import.vue'
|
||||
import Edit from './edit.vue'
|
||||
import Preview from './preview.vue'
|
||||
import Generator from './generator.vue'
|
||||
import { useTableSyncApi } from '@/api/table'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useDownloadApi } from '@/api/generator'
|
||||
|
||||
const state: IHooksOptions = reactive({
|
||||
dataListUrl: '/gen/table/page',
|
||||
deleteUrl: '/gen/table',
|
||||
queryForm: {
|
||||
name: ''
|
||||
}
|
||||
})
|
||||
|
||||
const importRef = ref()
|
||||
const editRef = ref()
|
||||
const previewRef = ref()
|
||||
const generatorRef = ref()
|
||||
|
||||
const importHandle = (id?: number) => {
|
||||
importRef.value.init(id)
|
||||
}
|
||||
|
||||
const editHandle = (id?: number) => {
|
||||
editRef.value.init(id)
|
||||
}
|
||||
const previewHandle = (tableId?: number) => {
|
||||
previewRef.value.init(tableId)
|
||||
}
|
||||
const generatorHandle = (id?: number) => {
|
||||
generatorRef.value.init(id)
|
||||
}
|
||||
|
||||
const downloadBatchHandle = () => {
|
||||
const tableIds = state.dataListSelections ? state.dataListSelections : []
|
||||
|
||||
if (tableIds.length === 0) {
|
||||
ElMessage.warning('请选择生成代码的表')
|
||||
return
|
||||
}
|
||||
|
||||
useDownloadApi(tableIds)
|
||||
}
|
||||
|
||||
const syncHandle = (row: any) => {
|
||||
ElMessageBox.confirm(`确定同步数据表${row.name}吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
useTableSyncApi(row.id).then(() => {
|
||||
ElMessage.success('同步成功')
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
const templateList = ref([
|
||||
{
|
||||
label: 'relation-demo',
|
||||
value: 'relation-demo'
|
||||
},
|
||||
{
|
||||
label: 'vue-fastapi-admin',
|
||||
value: 'vue-fastapi-admin'
|
||||
}
|
||||
])
|
||||
|
||||
const {
|
||||
getDataList,
|
||||
selectionChangeHandle,
|
||||
sizeChangeHandle,
|
||||
currentChangeHandle,
|
||||
deleteBatchHandle
|
||||
} = useCrud(state)
|
||||
</script>
|
||||
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<!-- 预览界面 -->
|
||||
<el-drawer v-model="preview.visible" title="编辑" :size="1200" :with-header="false">
|
||||
<el-row>
|
||||
<el-col :span="5">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
style="max-width: 600px; margin-top: 1rem"
|
||||
:data="treeData"
|
||||
:props="{
|
||||
children: 'children',
|
||||
label: 'label'
|
||||
}"
|
||||
@node-click="handleNodeClick"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="19">
|
||||
<el-tabs v-model="preview.activeName" @tab-click="handleTabClick">
|
||||
<el-tab-pane
|
||||
v-for="(item, key) in preview.data"
|
||||
:key="key"
|
||||
:label="item.fileName"
|
||||
:name="key"
|
||||
>
|
||||
<code-mirror
|
||||
v-model="item.content"
|
||||
height="calc(100vh - 40px - 40px - 15px)"
|
||||
></code-mirror>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, nextTick } from 'vue'
|
||||
import { ElLoading } from 'element-plus'
|
||||
import { usePreviewApi } from '@/api/generator'
|
||||
import CodeMirror from '@/components/codemirror/CodeMirror.vue'
|
||||
|
||||
const treeRef = ref(null)
|
||||
const preview = reactive({
|
||||
visible: false,
|
||||
title: '代码预览',
|
||||
data: {},
|
||||
activeName: ref(0)
|
||||
})
|
||||
|
||||
const init = async (tableId?: number) => {
|
||||
const loadingInstance = ElLoading.service({ fullscreen: true })
|
||||
try {
|
||||
let resData = await usePreviewApi(tableId)
|
||||
preview.activeName = 0
|
||||
preview.data = resData.data.map((item: previewItem) => {
|
||||
item.fileName = item.path.split('/').slice(-1)[0].split('\\').slice(-1)[0]
|
||||
return item
|
||||
})
|
||||
treeData.value = pathsToElTree(preview.data.map(item => item.path))
|
||||
preview.visible = true
|
||||
await nextTick()
|
||||
expandAllNodes()
|
||||
} finally {
|
||||
loadingInstance.close()
|
||||
}
|
||||
}
|
||||
|
||||
interface TreeNode {
|
||||
id: string
|
||||
label: string
|
||||
path: string
|
||||
children: TreeNode[]
|
||||
}
|
||||
|
||||
interface previewItem {
|
||||
path: string
|
||||
fileName: string
|
||||
content: string
|
||||
}
|
||||
|
||||
const treeData = ref<TreeNode[]>()
|
||||
|
||||
function insertPathIntoTree(
|
||||
tree: TreeNode[],
|
||||
parts: string[],
|
||||
path: string,
|
||||
index: number = 0
|
||||
): void {
|
||||
if (index >= parts.length) {
|
||||
return
|
||||
}
|
||||
const part = parts[index]
|
||||
let node = tree.find(n => n.label === part)
|
||||
if (!node) {
|
||||
node = {
|
||||
id: part,
|
||||
label: part,
|
||||
path: path,
|
||||
children: []
|
||||
}
|
||||
tree.push(node)
|
||||
}
|
||||
insertPathIntoTree(node.children, parts, path, index + 1)
|
||||
}
|
||||
|
||||
function pathsToElTree(paths: string[]): TreeNode[] {
|
||||
const tree: TreeNode[] = []
|
||||
paths.forEach(path => {
|
||||
const parts = path.split(/[\\/]/)
|
||||
insertPathIntoTree(tree, parts, path)
|
||||
})
|
||||
return tree
|
||||
}
|
||||
|
||||
const handleNodeClick = (node: TreeNode) => {
|
||||
const targetTab = preview.data.findIndex(item => item.path === node.path)
|
||||
if (targetTab !== -1) {
|
||||
preview.activeName = targetTab
|
||||
}
|
||||
// 展开两级节点
|
||||
const tree = treeRef.value
|
||||
if (tree) {
|
||||
const store = tree.store
|
||||
const nodeModel = store.getNode(node.id)
|
||||
if (nodeModel) {
|
||||
nodeModel.expanded = true
|
||||
if (nodeModel.childNodes) {
|
||||
nodeModel.childNodes.forEach(child => {
|
||||
child.expanded = true
|
||||
if (child.childNodes) {
|
||||
child.childNodes.forEach(grandChild => {
|
||||
grandChild.expanded = true
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleTabClick = (tab: { label: string }) => {
|
||||
const targetNode = findNodeInTree(treeData.value || [], tab.label)
|
||||
if (targetNode) {
|
||||
// 这里假设 el-tree 有展开节点的方法,具体方法名可能需要根据 el-tree 的实际 API 来调整
|
||||
// 例如:展开节点的方法可能是 expand(node),这里只是示例
|
||||
// expand(targetNode)
|
||||
}
|
||||
}
|
||||
|
||||
function findNodeInTree(tree: TreeNode[], label: string): TreeNode | null {
|
||||
for (const node of tree) {
|
||||
if (node.label === label) {
|
||||
return node
|
||||
}
|
||||
const childNode = findNodeInTree(node.children, label)
|
||||
if (childNode) {
|
||||
return childNode
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const expandAllNodes = () => {
|
||||
const tree = treeRef.value
|
||||
if (tree) {
|
||||
const store = tree.store
|
||||
const rootNodes = store.root.childNodes
|
||||
const expandNode = node => {
|
||||
node.expanded = true
|
||||
if (node.childNodes) {
|
||||
node.childNodes.forEach(child => {
|
||||
expandNode(child)
|
||||
})
|
||||
}
|
||||
}
|
||||
rootNodes.forEach(rootNode => {
|
||||
expandNode(rootNode)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
expandAllNodes()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="!dataForm.id ? '新增' : '修改'"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="dataForm"
|
||||
:rules="dataRules"
|
||||
label-width="100px"
|
||||
@keyup.enter="submitHandle()"
|
||||
>
|
||||
<el-form-item label="项目名" prop="projectName">
|
||||
<el-input v-model="dataForm.projectName" placeholder="项目名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目标识" prop="projectCode">
|
||||
<el-input v-model="dataForm.projectCode" placeholder="项目标识"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目包名" prop="projectPackage">
|
||||
<el-input v-model="dataForm.projectPackage" placeholder="项目包名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目路径" prop="projectPath">
|
||||
<el-input v-model="dataForm.projectPath" placeholder="项目路径"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitHandle()">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus/es'
|
||||
import { useProjectApi, useProjectSubmitApi } from '@/api/project'
|
||||
|
||||
const emit = defineEmits(['refreshDataList'])
|
||||
|
||||
const visible = ref(false)
|
||||
const dataFormRef = ref()
|
||||
|
||||
const dataForm = reactive({
|
||||
id: '',
|
||||
projectName: '',
|
||||
projectCode: '',
|
||||
projectPackage: '',
|
||||
projectPath: ''
|
||||
})
|
||||
|
||||
const init = (id?: number) => {
|
||||
visible.value = true
|
||||
dataForm.id = ''
|
||||
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
if (id) {
|
||||
getProject(id)
|
||||
}
|
||||
}
|
||||
|
||||
const getProject = (id: number) => {
|
||||
useProjectApi(id).then(res => {
|
||||
Object.assign(dataForm, res.data)
|
||||
})
|
||||
}
|
||||
|
||||
const dataRules = ref({
|
||||
projectName: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
projectCode: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
projectPackage: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
projectPath: [{ required: true, message: '必填项不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 表单提交
|
||||
const submitHandle = () => {
|
||||
dataFormRef.value.validate((valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false
|
||||
}
|
||||
|
||||
useProjectSubmitApi(dataForm).then(() => {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
visible.value = false
|
||||
emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="源码下载" :close-on-click-modal="false">
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="dataForm"
|
||||
:rules="dataRules"
|
||||
label-width="100px"
|
||||
@keyup.enter="submitHandle()"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目名" prop="projectName">
|
||||
<el-input v-model="dataForm.projectName" placeholder="项目名" disabled></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目路径" prop="projectPath">
|
||||
<el-input v-model="dataForm.projectPath" placeholder="项目路径" disabled></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目标识" prop="projectCode">
|
||||
<el-input v-model="dataForm.projectCode" placeholder="项目标识" disabled></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目包名" prop="projectPackage">
|
||||
<el-input v-model="dataForm.projectPackage" placeholder="项目包名" disabled></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider> 变更后的信息 </el-divider>
|
||||
|
||||
<el-form-item label="项目名" prop="modifyProjectName">
|
||||
<el-input v-model="dataForm.modifyProjectName" placeholder="项目名"></el-input>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目标识" prop="modifyProjectCode">
|
||||
<el-input v-model="dataForm.modifyProjectCode" placeholder="项目标识"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目包名" prop="modifyProjectPackage">
|
||||
<el-input v-model="dataForm.modifyProjectPackage" placeholder="项目包名"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排除文件" prop="exclusions">
|
||||
<el-input v-model="dataForm.exclusions" placeholder="排除文件"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="变更文件" prop="modifySuffix">
|
||||
<el-input v-model="dataForm.modifySuffix" placeholder="变更文件"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitHandle()">下载</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useProjectApi, useProjectSubmitApi, useSourceDownloadApi } from '@/api/project'
|
||||
|
||||
const visible = ref(false)
|
||||
const dataFormRef = ref()
|
||||
|
||||
const dataForm = reactive({
|
||||
id: '',
|
||||
projectName: '',
|
||||
projectCode: '',
|
||||
projectPackage: '',
|
||||
projectPath: '',
|
||||
modifyProjectName: '',
|
||||
modifyProjectCode: '',
|
||||
modifyProjectPackage: '',
|
||||
exclusions: '',
|
||||
modifySuffix: ''
|
||||
})
|
||||
|
||||
const init = (id?: number) => {
|
||||
visible.value = true
|
||||
dataForm.id = ''
|
||||
|
||||
// 重置表单数据
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
if (id) {
|
||||
getProject(id)
|
||||
}
|
||||
}
|
||||
|
||||
const getProject = (id: number) => {
|
||||
useProjectApi(id).then(res => {
|
||||
Object.assign(dataForm, res.data)
|
||||
})
|
||||
}
|
||||
|
||||
const dataRules = ref({
|
||||
modifyProjectName: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
modifyProjectCode: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
modifyProjectPackage: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
exclusions: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
|
||||
modifySuffix: [{ required: true, message: '必填项不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 表单提交
|
||||
const submitHandle = () => {
|
||||
dataFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 先保存
|
||||
await useProjectSubmitApi(dataForm)
|
||||
|
||||
// 源码下载
|
||||
useSourceDownloadApi(dataForm.id)
|
||||
visible.value = false
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="state.queryForm.projectName" placeholder="项目名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="danger" @click="deleteBatchHandle()">删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
v-loading="state.dataListLoading"
|
||||
:data="state.dataList"
|
||||
border
|
||||
style="width: 100%"
|
||||
@selection-change="selectionChangeHandle"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="50"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="projectCode"
|
||||
label="项目标识"
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="projectPackage"
|
||||
label="项目包名"
|
||||
show-overflow-tooltip
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="projectPath"
|
||||
label="项目路径"
|
||||
show-overflow-tooltip
|
||||
header-align="center"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" header-align="center" align="center" width="180">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="addOrUpdateHandle(scope.row.id)">修改</el-button>
|
||||
<el-button type="primary" link @click="downloadHandle(scope.row.id)">源码下载</el-button>
|
||||
<el-button type="primary" link @click="deleteBatchHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
:current-page="state.page"
|
||||
:page-sizes="state.pageSizes"
|
||||
:page-size="state.limit"
|
||||
:total="state.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
>
|
||||
</el-pagination>
|
||||
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update ref="addOrUpdateRef" @refresh-data-list="getDataList"></add-or-update>
|
||||
<!-- 源码下载 -->
|
||||
<download ref="downloadRef"></download>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useCrud } from '@/hooks'
|
||||
import { reactive, ref } from 'vue'
|
||||
import AddOrUpdate from './add-or-update.vue'
|
||||
import Download from './download.vue'
|
||||
import { IHooksOptions } from '@/hooks/interface'
|
||||
|
||||
const state: IHooksOptions = reactive({
|
||||
dataListUrl: '/gen/project/page',
|
||||
deleteUrl: '/gen/project',
|
||||
queryForm: {
|
||||
projectName: ''
|
||||
}
|
||||
})
|
||||
|
||||
const addOrUpdateRef = ref()
|
||||
const addOrUpdateHandle = (id?: number) => {
|
||||
addOrUpdateRef.value.init(id)
|
||||
}
|
||||
|
||||
const downloadRef = ref()
|
||||
const downloadHandle = (id?: number) => {
|
||||
downloadRef.value.init(id)
|
||||
}
|
||||
|
||||
const {
|
||||
getDataList,
|
||||
selectionChangeHandle,
|
||||
sizeChangeHandle,
|
||||
currentChangeHandle,
|
||||
deleteBatchHandle
|
||||
} = useCrud(state)
|
||||
</script>
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
"types": ["web-bluetooth", "vite/client"],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"compilerOptions": {
|
||||
"types": ["vite-plugin-svg-icons/client"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { resolve } from 'path'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
|
||||
import { convertEnv, getSrcPath, getRootPath } from './build/utils'
|
||||
import { viteDefine } from './build/config'
|
||||
import { OUTPUT_DIR, PROXY_CONFIG } from './build/constant'
|
||||
|
||||
export default defineConfig(({ command, mode }) => {
|
||||
const srcPath = getSrcPath()
|
||||
const rootPath = getRootPath()
|
||||
const isBuild = command === 'build'
|
||||
|
||||
const env = loadEnv(mode, process.cwd())
|
||||
const viteEnv = convertEnv(env)
|
||||
const { VITE_PORT, VITE_PUBLIC_PATH, VITE_USE_PROXY, VITE_BASE_API } = viteEnv
|
||||
|
||||
return {
|
||||
base: VITE_PUBLIC_PATH || '/',
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': rootPath,
|
||||
'@': srcPath,
|
||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
|
||||
}
|
||||
},
|
||||
define: viteDefine,
|
||||
plugins: [
|
||||
vue(),
|
||||
createSvgIconsPlugin({
|
||||
iconDirs: [resolve(__dirname, 'src/icons/svg')],
|
||||
symbolId: 'icon-[dir]-[name]'
|
||||
})
|
||||
],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: VITE_PORT,
|
||||
open: true,
|
||||
proxy: VITE_USE_PROXY
|
||||
? {
|
||||
[VITE_BASE_API]: PROXY_CONFIG[VITE_BASE_API]
|
||||
}
|
||||
: undefined
|
||||
},
|
||||
build: {
|
||||
target: 'es2015',
|
||||
outDir: OUTPUT_DIR || 'dist',
|
||||
reportCompressedSize: false, // 启用/禁用 gzip 压缩大小报告
|
||||
chunkSizeWarningLimit: 1024 // chunk 大小警告的限制(单位kb)
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user