Merge remote-tracking branch 'gva/main'

# Conflicts:
#	server/go.mod
#	server/go.sum
#	server/mcp/api_creator.go
#	server/mcp/execution_plan_schema.md
#	server/mcp/gva_auto_generate.go
#	server/mcp/menu_creator.go
#	server/mcp/requirement_analyzer.go
#	server/utils/verify.go
This commit is contained in:
2025-09-09 21:37:59 +08:00
71 changed files with 3376 additions and 715 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
// 运行项目前通过node执行此脚本 (此脚本与 node_modules 目录同级)
const fs = require('fs')
const path = require('path')
import fs from 'fs'
import path from 'path'
const wfPath = path.resolve(__dirname, './node_modules/.bin')
fs.readdir(wfPath, (err, files) => {
+1 -1
View File
@@ -3,7 +3,7 @@
未经授权的商用使用可能会被我们的资产搜索引擎爬取,并可能导致后续索赔。索赔金额将不低于高级授权费的十倍。请您遵守版权法律法规,尊重知识产权。
*/
var child_process = require('child_process')
import child_process from 'child_process'
var url = 'https://www.gin-vue-admin.com'
var cmd = ''
+9 -3
View File
@@ -1,6 +1,6 @@
{
"name": "gin-vue-admin",
"version": "2.8.4",
"version": "2.8.5",
"private": true,
"scripts": {
"serve": "node openDocument.js && vite --host --mode development",
@@ -9,10 +9,12 @@
"preview": "vite preview",
"fix-memory-limit": "cross-env LIMIT=4096 increase-memory-limit"
},
"type": "module",
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@form-create/designer": "^3.2.6",
"@form-create/element-ui": "^3.2.10",
"@unocss/transformer-directives": "^66.4.2",
"@vue-office/docx": "^1.6.2",
"@vue-office/excel": "^1.7.11",
"@vue-office/pdf": "^2.0.2",
@@ -39,10 +41,9 @@
"screenfull": "^6.0.2",
"sortablejs": "^1.15.3",
"spark-md5": "^3.0.2",
"tailwindcss": "^3.4.10",
"universal-cookie": "^7",
"vform3-builds": "^3.0.10",
"vite-auto-import-svg": "^1.6.0",
"vite-auto-import-svg": "^1.9.0",
"vue": "^3.5.7",
"vue-cropper": "^1.1.4",
"vue-echarts": "^7.0.3",
@@ -55,6 +56,9 @@
"devDependencies": {
"@babel/eslint-parser": "^7.25.1",
"@eslint/js": "^8.56.0",
"@unocss/extractor-svelte": "^66.4.2",
"@unocss/preset-wind3": "^66.4.2",
"@unocss/vite": "^66.5.0",
"@vitejs/plugin-legacy": "^6.0.0",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/cli-plugin-babel": "~5.0.8",
@@ -69,8 +73,10 @@
"dotenv": "^16.4.5",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.19.2",
"globals": "^16.3.0",
"sass": "^1.78.0",
"terser": "^5.31.6",
"unocss": "^66.4.2",
"vite": "^6.2.3",
"vite-plugin-banner": "^0.8.0",
"vite-plugin-importer": "^0.2.5",
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}
+7 -7
View File
@@ -7,13 +7,13 @@
<!-- 弹窗头部 -->
<div class="p-5 border-b border-gray-100 flex justify-between items-center">
<h3 class="text-lg font-semibold text-gray-800">{{ displayData.title }}</h3>
<button class="text-gray-400 hover:text-gray-600 transition-colors" @click="closeModal">
<close />
</button>
<div class="text-gray-400 hover:text-gray-600 transition-colors cursor-pointer" @click="closeModal">
<close class="h-6 w-6" />
</div>
</div>
<!-- 弹窗内容 -->
<div class="p-6">
<div class="p-6 pt-0">
<!-- 错误类型 -->
<div class="mb-4">
<div class="text-xs font-medium text-gray-500 uppercase mb-2">错误类型</div>
@@ -45,16 +45,16 @@
<!-- 弹窗底部 -->
<div class="py-2 px-4 border-t border-gray-100 flex justify-end">
<button class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium text-sm shadow-sm" @click="handleConfirm">
<div class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium text-sm shadow-sm cursor-pointer" @click="handleConfirm">
确定
</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits, ref, computed, onMounted } from 'vue';
import { defineProps, defineEmits, computed } from 'vue';
const props = defineProps({
errorData: {
@@ -27,6 +27,7 @@
import { ElMessage } from 'element-plus'
import { getUrl } from '@/utils/image'
import { useUserStore } from '@/pinia/modules/user'
const emits = defineEmits(['change', 'update:modelValue'])
@@ -35,6 +36,7 @@
emits('update:modelValue', valueHtml.value)
}
const userStore = useUserStore()
const props = defineProps({
modelValue: {
type: String,
@@ -53,6 +55,9 @@
editorConfig.MENU_CONF['uploadImage'] = {
fieldName: 'file',
server: basePath + '/fileUploadAndDownload/upload?noSave=1',
headers: {
'x-token': userStore.token,
},
customInsert(res, insertFn) {
if (res.code === 0) {
const urlPath = getUrl(res.data.file.url)
+1 -1
View File
@@ -47,7 +47,7 @@
const model = defineModel({ type: Array })
const fileList = ref(model.value)
const fileList = ref(model.value || [])
const emits = defineEmits(['on-success', 'on-error'])
+3 -1
View File
@@ -1,6 +1,8 @@
/**
* 网站配置文件
*/
import packageInfo from '../../package.json'
const greenText = (text) => `\x1b[32m${text}\x1b[0m`
const config = {
@@ -17,7 +19,7 @@ export const viteLogo = (env) => {
`> 欢迎使用Gin-Vue-Admin,开源地址:https://github.com/flipped-aurora/gin-vue-admin`
)
)
console.log(greenText(`> 当前版本:v2.8.4`))
console.log(greenText(`> 当前版本:v${packageInfo.version}`))
console.log(greenText(`> 加群方式:微信:shouzi_1994 QQ群:470239250`))
console.log(
greenText(`> 项目地址:https://github.com/flipped-aurora/gin-vue-admin`)
+3 -1
View File
@@ -4,13 +4,14 @@
* */
// 加载网站配置文件夹
import { register } from './global'
import packageInfo from '../../package.json'
export default {
install: (app) => {
register(app)
console.log(`
欢迎使用 Gin-Vue-Admin
当前版本:v2.8.4
当前版本:v${packageInfo.version}
加群方式:微信:shouzi_1994 QQ群:622360840
项目地址:https://github.com/flipped-aurora/gin-vue-admin
插件市场:https://plugin.gin-vue-admin.com
@@ -22,6 +23,7 @@ export default {
** 版权所有方:flipped-aurora开源团队 **
** 版权持有公司:北京翻转极光科技有限责任公司 **
** 剔除授权标识需购买商用授权:https://gin-vue-admin.com/empower/index.html **
** 感谢您对Gin-Vue-Admin的支持与关注 合法授权使用更有利于项目的长久发展**
`)
}
}
+1 -16
View File
@@ -7,22 +7,7 @@ export default {
// 当被绑定的元素插入到 DOM 中时……
mounted: function (el, binding) {
const userInfo = userStore.userInfo
let type = ''
switch (Object.prototype.toString.call(binding.value)) {
case '[object Array]':
type = 'Array'
break
case '[object String]':
type = 'String'
break
case '[object Number]':
type = 'Number'
break
default:
type = ''
break
}
if (type === '') {
if (!binding.value){
el.parentNode.removeChild(el)
return
}
+1
View File
@@ -1,5 +1,6 @@
import './style/element_visiable.scss'
import 'element-plus/theme-chalk/dark/css-vars.css'
import 'uno.css';
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
+1 -1
View File
@@ -93,7 +93,7 @@ export const useUserStore = defineStore('user', () => {
}
if (!router.hasRoute(userInfo.value.authority.defaultRouter)) {
ElMessage.error('请联系管理员进行授权')
ElMessage.error('不存在可以登陆的首页,请联系管理员进行配置')
} else {
await router.replace({ name: userInfo.value.authority.defaultRouter })
}
-3
View File
@@ -1,9 +1,6 @@
@use '@/style/main.scss';
@use '@/style/reset';
@tailwind base;
@tailwind components;
@tailwind utilities;
.el-button {
font-weight: 400;
+9 -1
View File
@@ -17,7 +17,15 @@
}
.gva-btn-list {
@apply mb-3 flex items-center;
@apply mb-3 flex items-center flex-wrap gap-2;
.el-button+.el-button{
@apply ml-0 !important;
}
.el-upload{
.el-button{
@apply ml-0 !important;
}
}
}
#nprogress .bar {
+13 -1
View File
@@ -24,6 +24,7 @@
<script setup>
import { useUserStore } from '@/pinia/modules/user'
import { useRouter } from 'vue-router'
import { emitter } from '@/utils/bus'
defineOptions({
name: 'Error'
@@ -32,6 +33,17 @@
const userStore = useUserStore()
const router = useRouter()
const toDashboard = () => {
router.push({ name: userStore.userInfo.authority.defaultRouter })
try {
router.push({ name: userStore.userInfo.authority.defaultRouter })
} catch (error) {
emitter.emit('show-error', {
code: '401',
message: "检测到其他用户修改了路由权限,请重新登录",
fn: () => {
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })
}
})
}
}
</script>
+20 -2
View File
@@ -136,7 +136,7 @@
// @ts-ignore
import { initDB } from '@/api/initdb'
import { reactive, ref } from 'vue'
import { ElLoading, ElMessage } from 'element-plus'
import { ElLoading, ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
defineOptions({
@@ -274,7 +274,25 @@
type: 'success',
message: res.msg
})
router.push({ name: 'Login' })
// 显示AI助手配置提示弹窗
ElMessageBox.confirm(
'已经完成基础数据库初始化!建议先进行编辑器AI助手配置,以获得更好的开发体验。',
'配置完成',
{
confirmButtonText: '查看AI配置文档',
cancelButtonText: '稍后配置',
type: 'success',
center: true
}
).then(() => {
// 点击确认按钮,打开AI配置文档
window.open('https://www.gin-vue-admin.com/guide/server/mcp.html', '_blank')
router.push({ name: 'Login' })
}).catch(() => {
// 点击取消按钮或关闭弹窗,直接跳转到登录页
router.push({ name: 'Login' })
})
}
loading.close()
} catch (_) {
@@ -7,7 +7,7 @@
<el-menu
:default-active="routerStore.topActive"
mode="horizontal"
class="border-r-0 border-b-0 w-full flex gap-1 items-center box-border h-[calc(100%-1px)]"
class="!border-r-0 border-b-0 w-full flex gap-1 items-center box-border h-[calc(100%-1px)]"
unique-opened
@select="(index, _, ele) => selectMenuItem(index, _, ele, true)"
>
@@ -34,7 +34,7 @@
:collapse="isCollapse"
:collapse-transition="false"
:default-active="active"
class="border-r-0 w-full"
class="!border-r-0 w-full"
unique-opened
@select="(index, _, ele) => selectMenuItem(index, _, ele, false)"
>
+1 -1
View File
@@ -6,7 +6,7 @@
<el-menu
:default-active="active"
mode="horizontal"
class="border-r-0 w-full flex gap-1 items-center box-border h-[calc(100%-1px)]"
class="!border-r-0 w-full flex gap-1 items-center box-border h-[calc(100%-1px)]"
unique-opened
:ellipsis="shouldEllipsis"
@select="selectMenuItem"
+1 -1
View File
@@ -11,7 +11,7 @@
:collapse="isCollapse"
:collapse-transition="false"
:default-active="active"
class="border-r-0 w-full"
class="!border-r-0 w-full"
unique-opened
@select="selectMenuItem"
>
+3 -3
View File
@@ -2,7 +2,7 @@
<div class="flex h-full">
<!-- 一级菜单常驻侧边栏 -->
<div
class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700"
class="relative !h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700"
:style="{
width: config.layout_side_collapsed_width + 'px'
}"
@@ -12,7 +12,7 @@
:collapse="true"
:collapse-transition="false"
:default-active="topActive"
class="border-r-0 w-full"
class="!border-r-0 w-full"
unique-opened
@select="selectTopMenuItem"
>
@@ -74,7 +74,7 @@
:collapse="isCollapse"
:collapse-transition="false"
:default-active="active"
class="border-r-0 w-full"
class="!border-r-0 w-full"
unique-opened
@select="selectMenuItem"
>
+6 -6
View File
@@ -8,7 +8,7 @@
<el-tooltip class="" effect="dark" content="视频教程" placement="bottom">
<el-dropdown @command="toDoc">
<el-icon
class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
class="w-8 h-8 p-2 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
>
<Film />
</el-icon>
@@ -29,7 +29,7 @@
<el-tooltip class="" effect="dark" content="搜索" placement="bottom">
<el-icon
@click="handleCommand"
class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
class="w-8 h-8 p-2 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
>
<Search />
</el-icon>
@@ -37,7 +37,7 @@
<el-tooltip class="" effect="dark" content="系统设置" placement="bottom">
<el-icon
class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
class="w-8 h-8 p-2 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
@click="toggleSetting"
>
<Setting />
@@ -46,7 +46,7 @@
<el-tooltip class="" effect="dark" content="刷新" placement="bottom">
<el-icon
class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
class="w-8 h-8 p-2 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
:class="showRefreshAnmite ? 'animate-spin' : ''"
@click="toggleRefresh"
>
@@ -61,14 +61,14 @@
>
<el-icon
v-if="appStore.isDark"
class="w-8 h-8 shadow rounded-full border border-gray-600 cursor-pointer border-solid"
class="w-8 h-8 p-2 shadow rounded-full border border-gray-600 cursor-pointer border-solid"
@click="appStore.toggleTheme(false)"
>
<Sunny />
</el-icon>
<el-icon
v-else
class="w-8 h-8 shadow rounded-full border border-gray-200 cursor-pointer border-solid"
class="w-8 h-8 p-2 shadow rounded-full border border-gray-200 cursor-pointer border-solid"
@click="appStore.toggleTheme(true)"
>
<Moon />
+2 -2
View File
@@ -7,11 +7,11 @@
:font="font"
:z-index="9999"
:gap="[180, 150]"
class="absolute inset-0 pointer-events-none"
class="!absolute !inset-0 !pointer-events-none"
:content="userStore.userInfo.nickName"
/>
<gva-header />
<div class="flex flex-row w-full gva-container pt-16 box-border h-full">
<div class="flex flex-row w-full gva-container pt-16 box-border !h-full">
<gva-aside
v-if="
config.side_mode === 'normal' || config.side_mode === 'sidebar' ||
+4 -4
View File
@@ -22,14 +22,14 @@
</div>
</template>
<div class="h-full bg-white dark:bg-gray-900">
<div class="bg-white dark:bg-gray-900">
<div class="px-8 pt-4 pb-6 border-b border-gray-200 dark:border-gray-700">
<div class="flex justify-center">
<div class="inline-flex bg-gray-100 dark:bg-gray-800 rounded-xl p-1.5 border border-gray-200 dark:border-gray-700 shadow-sm">
<button
<div
v-for="tab in tabs"
:key="tab.key"
class="px-6 py-3 text-base font-medium rounded-lg transition-all duration-150 ease-in-out min-w-[80px]"
class="px-6 py-3 text-base text-center cursor-pointer font-medium rounded-lg transition-all duration-150 ease-in-out min-w-[80px]"
:class="[
activeTab === tab.key
? 'text-white shadow-md transform -translate-y-0.5'
@@ -39,7 +39,7 @@
@click="activeTab = tab.key"
>
{{ tab.label }}
</button>
</div>
</div>
</div>
</div>
+1 -1
View File
@@ -8,7 +8,7 @@
v-model="activeValue"
:closable="!(historys.length === 1 && $route.name === defaultRouter)"
type="card"
class="bg-white text-slate-700 dark:text-slate-500 dark:bg-slate-900"
class="bg-white text-slate-700 dark:text-slate-500 dark:bg-slate-900 pt-1"
@contextmenu.prevent="openContextMenu($event)"
@tab-click="changeTab"
@tab-remove="removeTab"
@@ -23,7 +23,7 @@
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ node.label }}</span>
<span v-if="node.checked">
<span v-if="node.checked && !data.name?.startsWith('http://') && !data.name?.startsWith('https://')">
<el-button
type="primary"
link
@@ -6,7 +6,7 @@
:options="pathOptions"
v-model="activeComponent"
filterable
class="w-full"
class="!w-full"
clearable
@change="emitChange"
/>
+145 -59
View File
@@ -117,9 +117,9 @@
<el-input v-model="exportForm.description" type="textarea" placeholder="请输入版本描述" />
</el-form-item>
<el-form-item label="发版信息">
<div class="flex gap-5 w-full">
<div class="flex gap-3 w-full">
<!-- 菜单选择 -->
<div class="flex flex-col border border-gray-300 rounded overflow-hidden h-full flex-1 w-1/2">
<div class="flex flex-col border border-gray-300 rounded overflow-hidden h-full flex-1 w-1/3">
<div class="flex justify-between items-center px-4 py-3 bg-gray-50 border-b border-gray-300">
<span class="m-0 text-gray-800 text-base font-medium">选择菜单</span>
</div>
@@ -140,7 +140,7 @@
</div>
<!-- API选择 -->
<div class="flex flex-col border border-gray-300 rounded overflow-hidden h-full flex-1 w-1/2">
<div class="flex flex-col border border-gray-300 rounded overflow-hidden h-full flex-1 w-1/3">
<div class="flex justify-between items-center px-4 py-3 bg-gray-50 border-b border-gray-300">
<span class="m-0 text-gray-800 text-base font-medium">选择API</span>
</div>
@@ -153,7 +153,7 @@
<el-tree ref="apiTreeRef" :data="apiTreeData" :default-checked-keys="selectedApiIds"
:props="apiTreeProps" default-expand-all highlight-current node-key="onlyId" show-checkbox
:filter-node-method="filterApiNode" @check="onApiCheck" class="api-tree">
<template #default="{ _, data }">
<template #default="{ data }">
<div class="flex items-center justify-between w-full pr-1">
<span>{{ data.description }}</span>
<el-tooltip :content="data.path">
@@ -166,6 +166,32 @@
</el-tree>
</div>
</div>
<!-- 字典选择 -->
<div class="flex flex-col border border-gray-300 rounded overflow-hidden h-full flex-1 w-1/3">
<div class="flex justify-between items-center px-4 py-3 bg-gray-50 border-b border-gray-300">
<span class="m-0 text-gray-800 text-base font-medium">选择字典</span>
</div>
<div class="px-4 py-3 border-b border-gray-300 bg-gray-50">
<el-input v-model="dictFilterText" placeholder="输入关键字进行过滤" clearable size="small" />
</div>
<div class="flex-1 p-2 min-h-[300px] max-h-[400px] overflow-y-auto">
<el-tree ref="dictTreeRef" :data="dictTreeData" :default-checked-keys="selectedDictIds"
:props="dictTreeProps" default-expand-all highlight-current node-key="ID" show-checkbox
:filter-node-method="filterDictNode" @check="onDictCheck" class="dict-tree">
<template #default="{ data }">
<div class="flex items-center justify-between w-full pr-1">
<span>{{ data.name || data.label }}</span>
<el-tooltip :content="data.desc || (data.value ? `值: ${data.value}` : '')">
<span class="text-gray-500 text-xs ml-2">
{{ data.type || (data.value ? `值: ${data.value}` : '') }}
</span>
</el-tooltip>
</div>
</template>
</el-tree>
</div>
</div>
</div>
</el-form-item>
</el-form>
@@ -212,8 +238,8 @@
</el-form-item>
<el-form-item label="预览内容" v-if="importPreviewData">
<div class="flex flex-col flex-1 gap-4 border border-gray-300 rounded p-4 bg-gray-50">
<div class="flex gap-5 w-full">
<div class="border border-gray-300 rounded overflow-hidden flex-1 w-1/2">
<div class="flex gap-3 w-full">
<div class="border border-gray-300 rounded overflow-hidden flex-1 w-1/3">
<div class="flex flex-col border border-gray-300 rounded overflow-hidden h-full">
<div class="flex justify-between items-center px-4 py-3 bg-gray-50 border-b border-gray-300">
<h3 class="m-0 text-gray-800 text-base font-medium">菜单 ({{ getTotalMenuCount() }}项)</h3>
@@ -238,7 +264,7 @@
</div>
</div>
</div>
<div class="border border-gray-300 rounded overflow-hidden flex-1 w-1/2">
<div class="border border-gray-300 rounded overflow-hidden flex-1 w-1/3">
<div class="flex flex-col border border-gray-300 rounded overflow-hidden h-full">
<div class="flex justify-between items-center px-4 py-3 bg-gray-50 border-b border-gray-300">
<h3 class="m-0 text-gray-800 text-base font-medium">API ({{ importPreviewData.apis?.length || 0 }}项)</h3>
@@ -263,6 +289,33 @@
</div>
</div>
</div>
<div class="border border-gray-300 rounded overflow-hidden flex-1 w-1/3">
<div class="flex flex-col border border-gray-300 rounded overflow-hidden h-full">
<div class="flex justify-between items-center px-4 py-3 bg-gray-50 border-b border-gray-300">
<h3 class="m-0 text-gray-800 text-base font-medium">字典 ({{ importPreviewData.dictionaries?.length || 0 }}项)</h3>
</div>
<div class="flex-1 p-2 min-h-[300px] max-h-[400px] overflow-y-auto">
<el-tree
:data="previewDictTreeData"
:props="dictTreeProps"
node-key="ID"
:expand-on-click-node="false"
:check-on-click-node="false"
:show-checkbox="false"
default-expand-all
>
<template #default="{ data }">
<div class="flex-1 flex items-center justify-between text-sm pr-2">
<span>{{ data.name || data.label }}</span>
<span class="text-gray-500 text-xs ml-2">
{{ data.type || (data.value ? `值: ${data.value}` : '') }}
</span>
</div>
</template>
</el-tree>
</div>
</div>
</div>
</div>
</div>
</el-form-item>
@@ -286,14 +339,13 @@ import {
// 导入菜单和API相关接口
import { getMenuList } from '@/api/menu'
import { getApiList } from '@/api/api'
import { getSysDictionaryList } from '@/api/sysDictionary'
// 全量引入格式化工具 请按需保留
import { getDictFunc, formatDate, filterDict } from '@/utils/format'
import { formatDate } from '@/utils/format'
import { ElMessage, ElMessageBox } from 'element-plus'
import { UploadFilled } from '@element-plus/icons-vue'
import { ref, reactive, watch } from 'vue'
// 引入按钮权限标识
import { useBtnAuth } from '@/utils/btnAuth'
import { ref, watch } from 'vue'
import { useAppStore } from "@/pinia"
defineOptions({
@@ -313,21 +365,26 @@ const exportForm = ref({
versionCode: '',
description: '',
menuIds: [],
apiIds: []
apiIds: [],
dictIds: []
})
// 树形结构相关数据
const menuTreeData = ref([])
const apiTreeData = ref([])
const dictTreeData = ref([])
const selectedMenuIds = ref([])
const selectedApiIds = ref([])
const selectedDictIds = ref([])
const menuFilterText = ref('')
const apiFilterTextName = ref('')
const apiFilterTextPath = ref('')
const dictFilterText = ref('')
// 树形组件引用
const menuTreeRef = ref(null)
const apiTreeRef = ref(null)
const dictTreeRef = ref(null)
// 树形属性配置
const menuTreeProps = ref({
@@ -342,6 +399,21 @@ const apiTreeProps = ref({
label: 'description'
})
const dictTreeProps = ref({
children: 'sysDictionaryDetails',
label: function (data) {
// 如果是字典主项,显示字典名称
if (data.name) {
return data.name
}
// 如果是字典详情项,显示标签
if (data.label) {
return data.label
}
return '未知项'
}
})
// 导入相关数据
const importDialogVisible = ref(false)
const importLoading = ref(false)
@@ -350,36 +422,10 @@ const importPreviewData = ref(null)
const uploadRef = ref(null)
const previewMenuTreeData = ref([])
const previewApiTreeData = ref([])
const previewDictTreeData = ref([])
// 验证规则
const rule = reactive({
versionName: [{
required: true,
message: '请输入版本名称',
trigger: ['input', 'blur'],
},
{
whitespace: true,
message: '不能只输入空格',
trigger: ['input', 'blur'],
}
],
versionCode: [{
required: true,
message: '请输入版本号',
trigger: ['input', 'blur'],
},
{
whitespace: true,
message: '不能只输入空格',
trigger: ['input', 'blur'],
}
]
})
const elFormRef = ref()
const elSearchFormRef = ref()
// =========== 表格控制部分 ===========
@@ -549,6 +595,19 @@ const getMenuAndApiList = async () => {
}
}
// 获取字典列表
const getDictList = async () => {
try {
const dictRes = await getSysDictionaryList({ page: 1, pageSize: 9999 })
if (dictRes.code === 0) {
dictTreeData.value = dictRes.data || []
}
} catch (error) {
console.error('获取字典数据失败:', error)
ElMessage.error('获取字典数据失败')
}
}
// 构建API树形结构
const buildApiTree = (apis) => {
const apiObj = {}
@@ -595,6 +654,20 @@ const filterApiNode = (value, data) => {
return matchesName && matchesPath
}
const filterDictNode = (value, data) => {
if (!value) return true
const name = data.name || ''
const type = data.type || ''
const desc = data.desc || ''
const label = data.label || ''
const dataValue = data.value || ''
return name.indexOf(value) !== -1 ||
type.indexOf(value) !== -1 ||
desc.indexOf(value) !== -1 ||
label.indexOf(value) !== -1 ||
dataValue.indexOf(value) !== -1
}
const onMenuCheck = (data, checked) => {
if (checked.checkedKeys) {
selectedMenuIds.value = checked.checkedKeys
@@ -607,6 +680,12 @@ const onApiCheck = (data, checked) => {
}
}
const onDictCheck = (data, checked) => {
if (checked.checkedKeys) {
selectedDictIds.value = checked.checkedKeys
}
}
// 监听过滤文本变化
watch(menuFilterText, (val) => {
if (menuTreeRef.value) {
@@ -620,10 +699,17 @@ watch([apiFilterTextName, apiFilterTextPath], () => {
}
})
watch(dictFilterText, (val) => {
if (dictTreeRef.value) {
dictTreeRef.value.filter(val)
}
})
// 导出相关方法
const openExportDialog = async () => {
exportDialogVisible.value = true
await getMenuAndApiList()
await getDictList()
}
const closeExportDialog = () => {
@@ -633,13 +719,16 @@ const closeExportDialog = () => {
versionCode: '',
description: '',
menuIds: [],
apiIds: []
apiIds: [],
dictIds: []
}
selectedMenuIds.value = []
selectedApiIds.value = []
selectedDictIds.value = []
menuFilterText.value = ''
apiFilterTextName.value = ''
apiFilterTextPath.value = ''
dictFilterText.value = ''
}
const handleExport = async () => {
@@ -650,15 +739,18 @@ const handleExport = async () => {
exportLoading.value = true
try {
// 获取选中的菜单和API
// 获取选中的菜单、API和字典
const checkedMenus = menuTreeRef.value ? menuTreeRef.value.getCheckedNodes(false, true) : []
const checkedApis = apiTreeRef.value ? apiTreeRef.value.getCheckedNodes(true) : []
const checkedDicts = dictTreeRef.value ? dictTreeRef.value.getCheckedNodes(true) : []
const menuIds = checkedMenus.map(menu => menu.ID)
const apiIds = checkedApis.map(api => api.ID)
const dictIds = checkedDicts.map(dict => dict.ID)
exportForm.value.menuIds = menuIds
exportForm.value.apiIds = apiIds
exportForm.value.dictIds = dictIds
const res = await exportVersion(exportForm.value)
if (res.code !== 0) {
@@ -748,29 +840,14 @@ const getTotalMenuCount = () => {
return countMenus(importPreviewData.value.menus)
}
// 构建树形结构的辅助函数
const buildTreeData = (data, parentId = 0) => {
const tree = []
// 处理parentId可能为字符串"0"或数字0的情况
const targetParentId = parentId === 0 ? [0, "0"] : [parentId]
const items = data.filter(item => targetParentId.includes(item.parentId))
items.forEach(item => {
const children = buildTreeData(data, item.ID)
if (children.length > 0) {
item.children = children
}
tree.push(item)
})
return tree
}
const handleJsonContentChange = () => {
if (!importJsonContent.value.trim()) {
importPreviewData.value = null
previewMenuTreeData.value = []
previewApiTreeData.value = []
previewDictTreeData.value = []
return
}
@@ -780,7 +857,8 @@ const handleJsonContentChange = () => {
// 构建预览数据
importPreviewData.value = {
menus: data.menus || [],
apis: data.apis || []
apis: data.apis || [],
dictionaries: data.dictionaries || []
}
// 直接使用菜单数据,因为它已经是树形结构(包含children字段)
@@ -810,11 +888,19 @@ const handleJsonContentChange = () => {
} else {
previewApiTreeData.value = []
}
// 处理字典数据
if (data.dictionaries && data.dictionaries.length > 0) {
previewDictTreeData.value = data.dictionaries
} else {
previewDictTreeData.value = []
}
} catch (error) {
console.error('JSON解析失败:', error)
importPreviewData.value = null
previewMenuTreeData.value = []
previewApiTreeData.value = []
previewDictTreeData.value = []
}
}
-23
View File
@@ -1,23 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
important: true,
theme: {
extend: {
backgroundColor: {
main: '#F5F5F5'
},
textColor: {
active: 'var(--el-color-primary)'
},
boxShadowColor: {
active: 'var(--el-color-primary)'
},
borderColor: {
'table-border': 'var(--el-border-color-lighter)'
}
}
},
darkMode: 'class',
plugins: []
}
+26
View File
@@ -0,0 +1,26 @@
import { defineConfig } from '@unocss/vite';
import presetWind3 from '@unocss/preset-wind3';
import transformerDirectives from '@unocss/transformer-directives'
export default defineConfig({
theme: {
backgroundColor: {
main: '#F5F5F5'
},
textColor: {
active: 'var(--el-color-primary)'
},
boxShadowColor: {
active: 'var(--el-color-primary)'
},
borderColor: {
'table-border': 'var(--el-border-color-lighter)'
}
},
presets: [
presetWind3({ dark: 'class' })
],
transformers: [
transformerDirectives(),
],
})
+4 -1
View File
@@ -9,6 +9,8 @@ import vueDevTools from 'vite-plugin-vue-devtools'
import VueFilePathPlugin from './vitePlugin/componentName/index.js'
import { svgBuilder } from 'vite-auto-import-svg'
import { AddSecret } from './vitePlugin/secret'
import UnoCSS from '@unocss/vite'
// @see https://cn.vitejs.dev/config/
export default ({ mode }) => {
AddSecret('')
@@ -111,7 +113,8 @@ export default ({ mode }) => {
vuePlugin(),
svgBuilder(['./src/plugin/','./src/assets/icons/'],base, outDir,'assets', NODE_ENV),
[Banner(`\n Build based on gin-vue-admin \n Time : ${timestamp}`)],
VueFilePathPlugin('./src/pathInfo.json')
VueFilePathPlugin('./src/pathInfo.json'),
UnoCSS()
]
}
return config