first commit

This commit is contained in:
2026-06-25 17:41:06 +08:00
commit b2f23a933b
370 changed files with 30526 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# 资源公共路径,需要以 /开头和结尾
VITE_PUBLIC_PATH = './'
# 是否启用代理
VITE_USE_PROXY = true
# base api
VITE_BASE_API = '/api/v1'
+1
View File
@@ -0,0 +1 @@
VITE_BASE_API=.
+22
View File
@@ -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
+19
View File
@@ -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'
}
}
+31
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
# 使用pnpm,则需要
shamefully-hoist=true
+29
View File
@@ -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 | avoidx => x]
arrowParens: 'avoid',
// 使用默认的折行标准,可选值"[always|never|preserve]"
proseWrap: 'preserve',
// 指定HTML文件的全局空格敏感度,可选值"[css|strict|ignore]"
htmlWhitespaceSensitivity: 'css',
// 结尾时换行符,默认值lf,可选值"[auto|lf|crlf|cr]"
endOfLine: 'lf'
}
+19
View File
@@ -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
```
+13
View File
@@ -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_,
}
+1
View File
@@ -0,0 +1 @@
export * from './define'
+23
View File
@@ -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,
},
}
+15
View File
@@ -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
}
+41
View File
@@ -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
}
+46
View File
@@ -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__',
}),
]
+15
View File
@@ -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))
}
}
+14
View File
@@ -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()
+70
View File
@@ -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
}
+35
View File
@@ -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 {}
+14
View File
@@ -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>
+60
View File
@@ -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

+14
View File
@@ -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>
+17
View File
@@ -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)
}
}
+25
View File
@@ -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}`)
}
+17
View File
@@ -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)
}
}
+17
View File
@@ -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}`)
}
+18
View File
@@ -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
}
+21
View File
@@ -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>
+4
View File
@@ -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>
+164
View File
@@ -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
}
}
+33
View File
@@ -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
+1
View File
@@ -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

+2
View File
@@ -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>
+25
View File
@@ -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>
+28
View File
@@ -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')
+90
View File
@@ -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
})
+96
View File
@@ -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;
}
+3
View File
@@ -0,0 +1,3 @@
@import './variables.scss';
@import './layout.scss';
@import './element.scss';
+68
View File
@@ -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;
}
}
+20
View File
@@ -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;
}
+13
View File
@@ -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
}
+59
View File
@@ -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
+30
View File
@@ -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)
// }
// }
+45
View File
@@ -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()
}
+32
View File
@@ -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>
+108
View File
@@ -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>
+133
View File
@@ -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>
+104
View File
@@ -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>
+330
View File
@@ -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>
+238
View File
@@ -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>
+145
View File
@@ -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>
+167
View File
@@ -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>
+189
View File
@@ -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>
+102
View File
@@ -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>
+139
View File
@@ -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>
+114
View File
@@ -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>
+27
View File
@@ -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"]
}
+54
View File
@@ -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)
}
}
})