+
@@ -70,28 +70,28 @@ const presetErrors = {
title: '检测到接口错误',
type: '服务器发生内部错误',
icon: 'server',
- color: 'text-red-500',
+ color: 'text-red-500 dark:text-red-400',
tips: '此类错误内容常见于后台panic,请先查看后台日志,如果影响您正常使用可强制登出清理缓存'
},
404: {
title: '资源未找到',
type: 'Not Found',
icon: 'warn',
- color: 'text-orange-500',
+ color: 'text-orange-500 dark:text-orange-400',
tips: '此类错误多为接口未注册(或未重启)或者请求路径(方法)与api路径(方法)不符--如果为自动化代码请检查是否存在空格'
},
401: {
title: '身份认证失败',
type: '身份令牌无效',
icon: 'lock',
- color: 'text-purple-500',
+ color: 'text-purple-500 dark:text-purple-400',
tips: '您的身份认证已过期或无效,请重新登录。'
},
'network': {
title: '网络错误',
type: 'Network Error',
icon: 'fa-wifi-slash',
- color: 'text-gray-500',
+ color: 'text-gray-500 dark:text-gray-400',
tips: '无法连接到服务器,请检查您的网络连接。'
}
};
@@ -109,7 +109,7 @@ const displayData = computed(() => {
title: '未知错误',
type: '检测到请求错误',
icon: 'fa-question-circle',
- color: 'text-gray-400',
+ color: 'text-gray-400 dark:text-gray-300',
message: props.errorData.message || '发生了一个未知错误。',
tips: '请检查控制台获取更多信息。'
};
diff --git a/web/src/components/logo/index.vue b/web/src/components/logo/index.vue
new file mode 100644
index 0000000000..88c335b5a1
--- /dev/null
+++ b/web/src/components/logo/index.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+ GVA
+
+
diff --git a/web/src/components/svgIcon/svgIcon.vue b/web/src/components/svgIcon/svgIcon.vue
index a72a421e24..1783e44f2e 100644
--- a/web/src/components/svgIcon/svgIcon.vue
+++ b/web/src/components/svgIcon/svgIcon.vue
@@ -1,32 +1,44 @@
-
+
+
+
+
+
+
+
-
+})
+const attrs = useAttrs();
+
+const bindAttrs = computed(() => ({
+ class: (attrs.class) || '',
+ style: (attrs.style) || ''
+}))
+
diff --git a/web/src/core/config.js b/web/src/core/config.js
index 63a89edc66..6c63b15ff3 100644
--- a/web/src/core/config.js
+++ b/web/src/core/config.js
@@ -5,10 +5,10 @@ import packageInfo from '../../package.json'
const greenText = (text) => `\x1b[32m${text}\x1b[0m`
-const config = {
+export const config = {
appName: 'Gin-Vue-Admin',
- appLogo: 'logo.png',
showViteLogo: true,
+ KeepAliveTabs: true,
logs: []
}
diff --git a/web/src/core/error-handel.js b/web/src/core/error-handel.js
new file mode 100644
index 0000000000..9c06e95356
--- /dev/null
+++ b/web/src/core/error-handel.js
@@ -0,0 +1,51 @@
+function sendErrorTip(errorInfo) {
+ console.groupCollapsed(`捕获到错误: ${errorInfo.type}`);
+ console.log('错误类型:', errorInfo.type);
+ console.log('错误信息:', errorInfo.message);
+ console.log('调用栈:', errorInfo.stack);
+ if (errorInfo.component) {
+ console.log('组件名:', errorInfo.component.name)
+ console.log('组件地址:', errorInfo.component.__file)
+ }
+ if (errorInfo.vueInfo) console.log('Vue 信息:', errorInfo.vueInfo);
+ if (errorInfo.source) console.log('来源文件:', errorInfo.source);
+ if (errorInfo.lineno) console.log('行号:', errorInfo.lineno);
+ if (errorInfo.colno) console.log('列号:', errorInfo.colno);
+ console.groupEnd();
+}
+
+function initVueErrorHandler(app) {
+ app.config.errorHandler = (err, vm, info) => {
+ let errorType = 'Vue Error';
+
+ sendErrorTip({
+ type: errorType,
+ message: err.message,
+ stack: err.stack,
+ component: vm.$options || 'Unknown Vue Component',
+ vueInfo: info
+ });
+ };
+}
+
+function initJsErrorHandler() {
+ window.onerror = (message, source, lineno, colno, error) => {
+ let errorType = 'JS Error';
+
+ sendErrorTip({
+ type: errorType,
+ message: message,
+ stack: error ? error.stack : 'No stack available',
+ source: source,
+ lineno: lineno,
+ colno: colno
+ });
+
+ return false;
+ };
+}
+
+export function initErrorHandler(app) {
+ initVueErrorHandler(app)
+ initJsErrorHandler()
+}
diff --git a/web/src/core/global.js b/web/src/core/global.js
index b402b7053c..40c033f3f0 100644
--- a/web/src/core/global.js
+++ b/web/src/core/global.js
@@ -10,7 +10,7 @@ const createIconComponent = (name) => ({
name: 'SvgIcon',
render() {
return h(svgIcon, {
- name: name
+ localIcon: name
})
}
})
@@ -21,6 +21,7 @@ const registerIcons = async (app) => {
'@/plugin/**/assets/icons/**/*.svg'
) // 插件目录 svg 图标
const mergedIconModules = Object.assign({}, iconModules, pluginIconModules) // 合并所有 svg 图标
+ let allKeys = []
for (const path in mergedIconModules) {
let pluginName = ''
if (path.startsWith('/src/plugin/')) {
@@ -36,16 +37,19 @@ const registerIcons = async (app) => {
continue
}
const key = `${pluginName}${iconName}`
- // 开发模式下列出所有 svg 图标,方便开发者直接查找复制使用
- import.meta.env.MODE == 'development' &&
- console.log(`svg-icon-component: <${key} />`)
const iconComponent = createIconComponent(key)
config.logs.push({
key: key,
label: key
})
app.component(key, iconComponent)
+
+ // 开发模式下列出所有 svg 图标,方便开发者直接查找复制使用
+ allKeys.push(key)
}
+
+ import.meta.env.MODE == 'development' &&
+ console.log(`所有可用的本地图标: ${allKeys.join(', ')}`)
}
export const register = (app) => {
diff --git a/web/src/directive/clickOutSide.js b/web/src/directive/clickOutSide.js
new file mode 100644
index 0000000000..d1bd5022be
--- /dev/null
+++ b/web/src/directive/clickOutSide.js
@@ -0,0 +1,43 @@
+export default {
+ install: (app) => {
+ app.directive('click-outside', {
+ mounted(el, binding) {
+ const handler = (e) => {
+ // 如果绑定的元素包含事件目标,或元素已经被移除,则不触发
+ if (!el || el.contains(e.target) || e.target === el) return
+ // 支持函数或对象 { handler: fn, exclude: [el1, el2], capture: true }
+ const value = binding.value
+ if (value && typeof value === 'object') {
+ if (
+ value.exclude &&
+ value.exclude.some(
+ (ex) => ex && ex.contains && ex.contains(e.target)
+ )
+ )
+ return
+ if (typeof value.handler === 'function') value.handler(e)
+ } else if (typeof value === 'function') {
+ value(e)
+ }
+ }
+
+ // 存到 el 上,便于解绑
+ el.__clickOutsideHandler__ = handler
+
+ // 延迟注册,避免 mounted 时触发(比如当点击就是触发绑定动作时)
+ setTimeout(() => {
+ document.addEventListener('mousedown', handler)
+ document.addEventListener('touchstart', handler)
+ }, 0)
+ },
+ unmounted(el) {
+ const h = el.__clickOutsideHandler__
+ if (h) {
+ document.removeEventListener('mousedown', h)
+ document.removeEventListener('touchstart', h)
+ delete el.__clickOutsideHandler__
+ }
+ }
+ })
+ }
+}
diff --git a/web/src/main.js b/web/src/main.js
index 18ba0723e4..1ba53c9e2d 100644
--- a/web/src/main.js
+++ b/web/src/main.js
@@ -1,6 +1,6 @@
import './style/element_visiable.scss'
import 'element-plus/theme-chalk/dark/css-vars.css'
-import 'uno.css';
+import 'uno.css'
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
@@ -12,11 +12,24 @@ import router from '@/router/index'
import '@/permission'
import run from '@/core/gin-vue-admin.js'
import auth from '@/directive/auth'
+import clickOutSide from '@/directive/clickOutSide'
import { store } from '@/pinia'
import App from './App.vue'
+import { initErrorHandler } from '@/core/error-handel'
+
const app = createApp(App)
+
+// 注入错误处理捕获
+initErrorHandler(app)
app.config.productionTip = false
-app.use(run).use(ElementPlus).use(store).use(auth).use(router).mount('#app')
+app
+ .use(run)
+ .use(ElementPlus)
+ .use(store)
+ .use(auth)
+ .use(clickOutSide)
+ .use(router)
+ .mount('#app')
export default app
diff --git a/web/src/permission.js b/web/src/permission.js
index 5276aea53c..d2dd6ba744 100644
--- a/web/src/permission.js
+++ b/web/src/permission.js
@@ -15,13 +15,113 @@ Nprogress.configure({
// 白名单路由
const WHITE_LIST = ['Login', 'Init']
+function isExternalUrl(val) {
+ return typeof val === 'string' && /^(https?:)?\/\//.test(val)
+}
+
+// 工具函数:统一路径归一化
+function normalizeAbsolutePath(p) {
+ const s = '/' + String(p || '')
+ return s.replace(/\/+/g, '/')
+}
+
+function normalizeRelativePath(p) {
+ return String(p || '').replace(/^\/+/, '')
+}
+
+// 安全注册:仅在路由名未存在时注册顶级路由
+function addTopLevelIfAbsent(r) {
+ if (!router.hasRoute(r.name)) {
+ router.addRoute(r)
+ }
+}
+
+// 将 n 级菜单扁平化为:
+// - 常规:一级 layout + 二级页面组件
+// - 若某节点 meta.defaultMenu === true:该节点为顶级(不包裹在 layout 下),其子节点作为该顶级的二级页面组件
+function addRouteByChildren(route, segments = [], parentName = null) {
+ // 跳过外链根节点
+ if (isExternalUrl(route?.path) || isExternalUrl(route?.name) || isExternalUrl(route?.component)) {
+ return
+ }
+
+ // 顶层 layout 仅用于承载,不参与路径拼接
+ if (route?.name === 'layout') {
+ route.children?.forEach((child) => addRouteByChildren(child, [], null))
+ return
+ }
+
+ // 如果标记为 defaultMenu,则该路由应作为顶级路由(不包裹在 layout 下)
+ if (route?.meta?.defaultMenu === true && parentName === null) {
+ const fullPath = [...segments, route.path].filter(Boolean).join('/')
+ const children = route.children ? [...route.children] : []
+ const newRoute = { ...route, path: fullPath }
+ delete newRoute.children
+ delete newRoute.parent
+ // 顶级路由使用绝对路径
+ newRoute.path = normalizeAbsolutePath(newRoute.path)
+
+ // 若已存在同名路由则整体跳过(之前应已处理过其子节点)
+ if (router.hasRoute(newRoute.name)) return
+ addTopLevelIfAbsent(newRoute)
+
+ // 若该 defaultMenu 节点仍有子节点,继续递归处理其子节点(挂载到该顶级路由下)
+ if (children.length) {
+ // 重置片段,使其成为顶级下的二级相对路径
+ children.forEach((child) => addRouteByChildren(child, [], newRoute.name))
+ }
+ return
+ }
+
+ // 还有子节点,继续向下收集路径片段(忽略外链片段)
+ if (route?.children && route.children.length) {
+ const nextSegments = isExternalUrl(route.path) ? segments : [...segments, route.path]
+ route.children.forEach((child) => addRouteByChildren(child, nextSegments, parentName))
+ return
+ }
+
+ // 叶子节点:注册为其父(defaultMenu 顶级或 layout)的二级子路由
+ const fullPath = [...segments, route.path].filter(Boolean).join('/')
+ const newRoute = { ...route, path: fullPath }
+ delete newRoute.children
+ delete newRoute.parent
+ // 子路由使用相对路径,避免 /layout/layout/... 的问题
+ newRoute.path = normalizeRelativePath(newRoute.path)
+
+ if (parentName) {
+ // 挂载到 defaultMenu 顶级路由下
+ router.addRoute(parentName, newRoute)
+ } else {
+ // 常规:挂载到 layout 下
+ router.addRoute('layout', newRoute)
+ }
+}
+
// 处理路由加载
const setupRouter = async (userStore) => {
try {
const routerStore = useRouterStore()
await Promise.all([routerStore.SetAsyncRouter(), userStore.GetUserInfo()])
- routerStore.asyncRouters.forEach((route) => router.addRoute(route))
+ // 确保先注册父级 layout
+ const baseRouters = routerStore.asyncRouters || []
+ const layoutRoute = baseRouters[0]
+ if (layoutRoute?.name === 'layout' && !router.hasRoute('layout')) {
+ const bareLayout = { ...layoutRoute, children: [] }
+ router.addRoute(bareLayout)
+ }
+
+ // 扁平化:将 layout.children 与其余顶层异步路由一并作为二级子路由注册到 layout 下
+ const toRegister = []
+ if (layoutRoute?.children?.length) {
+ toRegister.push(...layoutRoute.children)
+ }
+ if (baseRouters.length > 1) {
+ baseRouters.slice(1).forEach((r) => {
+ if (r?.name !== 'layout') toRegister.push(r)
+ })
+ }
+ toRegister.forEach((r) => addRouteByChildren(r, [], null))
return true
} catch (error) {
console.error('Setup router failed:', error)
diff --git a/web/src/pinia/modules/dictionary.js b/web/src/pinia/modules/dictionary.js
index 57a2844970..b0c089f962 100644
--- a/web/src/pinia/modules/dictionary.js
+++ b/web/src/pinia/modules/dictionary.js
@@ -1,4 +1,5 @@
import { findSysDictionary } from '@/api/sysDictionary'
+import { getDictionaryTreeListByType } from '@/api/sysDictionaryDetail'
import { defineStore } from 'pinia'
import { ref } from 'vue'
@@ -10,25 +11,235 @@ export const useDictionaryStore = defineStore('dictionary', () => {
dictionaryMap.value = { ...dictionaryMap.value, ...dictionaryRes }
}
- const getDictionary = async (type) => {
- if (dictionaryMap.value[type] && dictionaryMap.value[type].length) {
- return dictionaryMap.value[type]
+ // 过滤树形数据的深度
+ const filterTreeByDepth = (items, currentDepth, targetDepth) => {
+ if (targetDepth === 0) {
+ // depth=0 返回全部数据
+ return items
+ }
+
+ if (currentDepth >= targetDepth) {
+ // 达到目标深度,移除children
+ return items.map((item) => ({
+ label: item.label,
+ value: item.value,
+ extend: item.extend
+ }))
+ }
+
+ // 递归处理子项
+ return items.map((item) => ({
+ label: item.label,
+ value: item.value,
+ extend: item.extend,
+ children: item.children
+ ? filterTreeByDepth(item.children, currentDepth + 1, targetDepth)
+ : undefined
+ }))
+ }
+
+ // 将树形结构扁平化为数组(用于兼容原有的平铺格式)
+ const flattenTree = (items) => {
+ const result = []
+
+ const traverse = (nodes) => {
+ nodes.forEach((item) => {
+ result.push({
+ label: item.label,
+ value: item.value,
+ extend: item.extend
+ })
+
+ if (item.children && item.children.length > 0) {
+ traverse(item.children)
+ }
+ })
+ }
+
+ traverse(items)
+ return result
+ }
+
+ // 标准化树形数据,确保每个节点都包含标准的字段格式
+ const normalizeTreeData = (items) => {
+ return items.map((item) => ({
+ label: item.label,
+ value: item.value,
+ extend: item.extend,
+ children:
+ item.children && item.children.length > 0
+ ? normalizeTreeData(item.children)
+ : undefined
+ }))
+ }
+
+ // 根据value和depth查找指定节点并返回其children
+ const findNodeByValue = (
+ items,
+ targetValue,
+ currentDepth = 1,
+ maxDepth = 0
+ ) => {
+ for (const item of items) {
+ // 如果找到目标value的节点
+ if (item.value === targetValue) {
+ // 如果maxDepth为0,返回所有children
+ if (maxDepth === 0) {
+ return item.children ? normalizeTreeData(item.children) : []
+ }
+ // 否则根据depth限制返回children
+ if (item.children && item.children.length > 0) {
+ return filterTreeByDepth(item.children, 1, maxDepth)
+ }
+ return []
+ }
+
+ // 如果当前深度小于最大深度,继续在children中查找
+ if (
+ item.children &&
+ item.children.length > 0 &&
+ (maxDepth === 0 || currentDepth < maxDepth)
+ ) {
+ const result = findNodeByValue(
+ item.children,
+ targetValue,
+ currentDepth + 1,
+ maxDepth
+ )
+ if (result !== null) {
+ return result
+ }
+ }
+ }
+ return null
+ }
+
+ const getDictionary = async (type, depth = 0, value = null) => {
+ // 如果传入了value参数,则查找指定节点的children
+ if (value !== null) {
+ // 构建缓存key,包含value和depth信息
+ const cacheKey = `${type}_value_${value}_depth_${depth}`
+
+ if (
+ dictionaryMap.value[cacheKey] &&
+ dictionaryMap.value[cacheKey].length
+ ) {
+ return dictionaryMap.value[cacheKey]
+ }
+
+ try {
+ // 获取完整的树形结构数据
+ const treeRes = await getDictionaryTreeListByType({ type })
+ if (
+ treeRes.code === 0 &&
+ treeRes.data &&
+ treeRes.data.list &&
+ treeRes.data.list.length > 0
+ ) {
+ // 查找指定value的节点并返回其children
+ const targetNodeChildren = findNodeByValue(
+ treeRes.data.list,
+ value,
+ 1,
+ depth
+ )
+
+ if (targetNodeChildren !== null) {
+ let resultData
+ if (depth === 0) {
+ // depth=0 时返回完整的children树形结构
+ resultData = targetNodeChildren
+ } else {
+ // 其他depth值:扁平化children数据
+ resultData = flattenTree(targetNodeChildren)
+ }
+
+ const dictionaryRes = {}
+ dictionaryRes[cacheKey] = resultData
+ setDictionaryMap(dictionaryRes)
+ return dictionaryMap.value[cacheKey]
+ } else {
+ // 如果没找到指定value的节点,返回空数组
+ return []
+ }
+ }
+ } catch (error) {
+ console.error('根据value获取字典数据失败:', error)
+ return []
+ }
+ }
+
+ // 原有的逻辑:不传value参数时的处理
+ // 构建缓存key,包含depth信息
+ const cacheKey = depth === 0 ? `${type}_tree` : `${type}_depth_${depth}`
+
+ if (dictionaryMap.value[cacheKey] && dictionaryMap.value[cacheKey].length) {
+ return dictionaryMap.value[cacheKey]
} else {
- const res = await findSysDictionary({ type })
- if (res.code === 0) {
- const dictionaryRes = {}
- const dict = []
- res.data.resysDictionary.sysDictionaryDetails &&
- res.data.resysDictionary.sysDictionaryDetails.forEach((item) => {
- dict.push({
- label: item.label,
- value: item.value,
- extend: item.extend
+ try {
+ // 首先尝试获取树形结构数据
+ const treeRes = await getDictionaryTreeListByType({ type })
+ if (
+ treeRes.code === 0 &&
+ treeRes.data &&
+ treeRes.data.list &&
+ treeRes.data.list.length > 0
+ ) {
+ // 使用树形结构数据
+ const treeData = treeRes.data.list
+
+ let resultData
+ if (depth === 0) {
+ // depth=0 时返回完整的树形结构,但要确保字段格式标准化
+ resultData = normalizeTreeData(treeData)
+ } else {
+ // 其他depth值:根据depth参数过滤数据,然后扁平化
+ const filteredData = filterTreeByDepth(treeData, 1, depth)
+ resultData = flattenTree(filteredData)
+ }
+
+ const dictionaryRes = {}
+ dictionaryRes[cacheKey] = resultData
+ setDictionaryMap(dictionaryRes)
+ return dictionaryMap.value[cacheKey]
+ } else {
+ // 如果没有树形数据,回退到原有的平铺方式
+ const res = await findSysDictionary({ type })
+ if (res.code === 0) {
+ const dictionaryRes = {}
+ const dict = []
+ res.data.resysDictionary.sysDictionaryDetails &&
+ res.data.resysDictionary.sysDictionaryDetails.forEach((item) => {
+ dict.push({
+ label: item.label,
+ value: item.value,
+ extend: item.extend
+ })
+ })
+ dictionaryRes[cacheKey] = dict
+ setDictionaryMap(dictionaryRes)
+ return dictionaryMap.value[cacheKey]
+ }
+ }
+ } catch (error) {
+ console.error('获取字典数据失败:', error)
+ // 发生错误时回退到原有方式
+ const res = await findSysDictionary({ type })
+ if (res.code === 0) {
+ const dictionaryRes = {}
+ const dict = []
+ res.data.resysDictionary.sysDictionaryDetails &&
+ res.data.resysDictionary.sysDictionaryDetails.forEach((item) => {
+ dict.push({
+ label: item.label,
+ value: item.value,
+ extend: item.extend
+ })
})
- })
- dictionaryRes[res.data.resysDictionary.type] = dict
- setDictionaryMap(dictionaryRes)
- return dictionaryMap.value[type]
+ dictionaryRes[cacheKey] = dict
+ setDictionaryMap(dictionaryRes)
+ return dictionaryMap.value[cacheKey]
+ }
}
}
}
diff --git a/web/src/pinia/modules/router.js b/web/src/pinia/modules/router.js
index ff1eb76138..c5c49ea08b 100644
--- a/web/src/pinia/modules/router.js
+++ b/web/src/pinia/modules/router.js
@@ -5,6 +5,7 @@ import { defineStore } from 'pinia'
import { ref, watchEffect } from 'vue'
import pathInfo from '@/pathInfo.json'
import {useRoute} from "vue-router";
+import {config} from "@/core/config.js";
const notLayoutRouterArr = []
const keepAliveRoutersArr = []
@@ -55,24 +56,24 @@ export const useRouterStore = defineStore('router', () => {
// 1. 首先添加原有的keepAlive配置
keepArrTemp.push(...keepAliveRoutersArr)
-
- history.forEach((item) => {
- // 2. 为所有history中的路由强制启用keep-alive
- // 通过routeMap获取路由信息,然后通过pathInfo获取组件名
- const routeInfo = routeMap[item.name]
- if (routeInfo && routeInfo.meta && routeInfo.meta.path) {
- const componentName = pathInfo[routeInfo.meta.path]
- if (componentName) {
- keepArrTemp.push(componentName)
+ if (config.KeepAliveTabs) {
+ history.forEach((item) => {
+ // 2. 为所有history中的路由强制启用keep-alive
+ // 通过routeMap获取路由信息,然后通过pathInfo获取组件名
+ const routeInfo = routeMap[item.name]
+ if (routeInfo && routeInfo.meta && routeInfo.meta.path) {
+ const componentName = pathInfo[routeInfo.meta.path]
+ if (componentName) {
+ keepArrTemp.push(componentName)
+ }
}
- }
-
- // 3. 如果子路由在tabs中打开,父路由也需要keepAlive
- if (nameMap[item.name]) {
- keepArrTemp.push(nameMap[item.name])
- }
- })
-
+
+ // 3. 如果子路由在tabs中打开,父路由也需要keepAlive
+ if (nameMap[item.name]) {
+ keepArrTemp.push(nameMap[item.name])
+ }
+ })
+ }
keepAliveRouters.value = Array.from(new Set(keepArrTemp))
}
diff --git a/web/src/style/reset.scss b/web/src/style/reset.scss
index 55b476fa24..fe879bf76a 100644
--- a/web/src/style/reset.scss
+++ b/web/src/style/reset.scss
@@ -1,136 +1,137 @@
-/* Document
- ========================================================================== */
-
-/**
- * 1. Correct the line height in all browsers.
- * 2. Prevent adjustments of font size after orientation changes in iOS.
- */
-
-html {
- line-height: 1.15;
- /* 1 */
- -webkit-text-size-adjust: 100%;
- /* 2 */
-}
-
-/* Sections
- ========================================================================== */
-
-/**
- * Remove the margin in all browsers.
- */
+/*
+1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
+2. [UnoCSS]: allow to override the default border color with css var `--un-default-border-color`
+*/
-body {
- margin: 0;
+*,
+::before,
+::after {
+ box-sizing: border-box; /* 1 */
+ border-width: 0; /* 2 */
+ border-style: solid; /* 2 */
+ border-color: var(--un-default-border-color, #e5e7eb); /* 2 */
}
-/**
- * Render the `main` element consistently in IE.
- */
+/*
+1. Use a consistent sensible line-height in all browsers.
+2. Prevent adjustments of font size after orientation changes in iOS.
+3. Use a more readable tab size.
+4. Use the user's configured `sans` font-family by default.
+*/
-main {
- display: block;
+html {
+ line-height: 1.5; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+ -moz-tab-size: 4; /* 3 */
+ tab-size: 4; /* 3 */
+ font-family:
+ ui-sans-serif,
+ system-ui,
+ -apple-system,
+ BlinkMacSystemFont,
+ 'Segoe UI',
+ Roboto,
+ 'Helvetica Neue',
+ Arial,
+ 'Noto Sans',
+ sans-serif,
+ 'Apple Color Emoji',
+ 'Segoe UI Emoji',
+ 'Segoe UI Symbol',
+ 'Noto Color Emoji'; /* 4 */
+
+ // TODO: 在下一个大版本更新的时候需要改回正确的16px
+ font-size: 14px;
}
-/**
- * Correct the font size and margin on `h1` elements within `section` and
- * `article` contexts in Chrome, Firefox, and Safari.
- */
+/*
+1. Remove the margin in all browsers.
+2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
+*/
-h1 {
- font-size: 2em;
- margin: 0.67em 0;
+body {
+ margin: 0; /* 1 */
+ line-height: inherit; /* 2 */
}
-/* Grouping content
- ========================================================================== */
-
-/**
- * 1. Add the correct box sizing in Firefox.
- * 2. Show the overflow in Edge and IE.
- */
+/*
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+3. Ensure horizontal rules are visible by default.
+*/
hr {
- box-sizing: content-box;
- /* 1 */
- height: 0;
- /* 1 */
- overflow: visible;
- /* 2 */
+ height: 0; /* 1 */
+ color: inherit; /* 2 */
+ border-top-width: 1px; /* 3 */
}
-/**
- * 1. Correct the inheritance and scaling of font size in all browsers.
- * 2. Correct the odd `em` font sizing in all browsers.
- */
+/*
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
-pre {
- font-family: monospace, monospace;
- /* 1 */
- font-size: 1em;
- /* 2 */
+abbr:where([title]) {
+ text-decoration: underline dotted;
}
-/* Text-level semantics
- ========================================================================== */
-
-/**
- * Remove the gray background on active links in IE 10.
- */
+/*
+Remove the default font size and weight for headings.
+*/
-a {
- background-color: transparent;
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: inherit;
+ font-weight: inherit;
}
-/**
- * 1. Remove the bottom border in Chrome 57-
- * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
- */
+/*
+Reset links to optimize for opt-in styling instead of opt-out.
+*/
-abbr[title] {
- border-bottom: none;
- /* 1 */
- text-decoration: underline;
- /* 2 */
- text-decoration: underline dotted;
- /* 2 */
+a {
+ color: inherit;
+ text-decoration: inherit;
}
-/**
- * Add the correct font weight in Chrome, Edge, and Safari.
- */
+/*
+Add the correct font weight in Edge and Safari.
+*/
b,
strong {
font-weight: bolder;
}
-/**
- * 1. Correct the inheritance and scaling of font size in all browsers.
- * 2. Correct the odd `em` font sizing in all browsers.
- */
+/*
+1. Use the user's configured `mono` font family by default.
+2. Correct the odd `em` font sizing in all browsers.
+*/
code,
kbd,
-samp {
- font-family: monospace, monospace;
- /* 1 */
- font-size: 1em;
- /* 2 */
+samp,
+pre {
+ font-family:
+ ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; /* 1 */
+ font-size: 1em; /* 2 */
}
-/**
- * Add the correct font size in all browsers.
- */
+/*
+Add the correct font size in all browsers.
+*/
small {
font-size: 80%;
}
-/**
- * Prevent `sub` and `sup` elements from affecting the line height in
- * all browsers.
- */
+/*
+Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
sub,
sup {
@@ -148,322 +149,233 @@ sup {
top: -0.5em;
}
-/* Embedded content
- ========================================================================== */
-
-/**
- * Remove the border on images inside links in IE 10.
- */
+/*
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+3. Remove gaps between table borders by default.
+*/
-img {
- border-style: none;
+table {
+ text-indent: 0; /* 1 */
+ border-color: inherit; /* 2 */
+ border-collapse: collapse; /* 3 */
}
-/* Forms
- ========================================================================== */
-
-/**
- * 1. Change the font styles in all browsers.
- * 2. Remove the margin in Firefox and Safari.
- */
+/*
+1. Change the font styles in all browsers.
+2. Remove the margin in Firefox and Safari.
+3. Remove default padding in all browsers.
+*/
button,
input,
optgroup,
select,
textarea {
- font-family: inherit;
- /* 1 */
- font-size: 100%;
- /* 1 */
- line-height: 1.15;
- /* 1 */
- margin: 0;
- /* 2 */
-}
-
-/**
- * Show the overflow in IE.
- * 1. Show the overflow in Edge.
- */
-
-button,
-input {
- /* 1 */
- overflow: visible;
+ font-family: inherit; /* 1 */
+ font-feature-settings: inherit; /* 1 */
+ font-variation-settings: inherit; /* 1 */
+ font-size: 100%; /* 1 */
+ font-weight: inherit; /* 1 */
+ line-height: inherit; /* 1 */
+ color: inherit; /* 1 */
+ margin: 0; /* 2 */
+ padding: 0; /* 3 */
}
-/**
- * Remove the inheritance of text transform in Edge, Firefox, and IE.
- * 1. Remove the inheritance of text transform in Firefox.
- */
+/*
+Remove the inheritance of text transform in Edge and Firefox.
+*/
button,
select {
- /* 1 */
text-transform: none;
}
-/**
- * Correct the inability to style clickable types in iOS and Safari.
- */
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Remove default button styles.
+*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
- -webkit-appearance: button;
-}
-
-/**
- * Remove the inner border and padding in Firefox.
- */
-
-button::-moz-focus-inner,
-[type='button']::-moz-focus-inner,
-[type='reset']::-moz-focus-inner,
-[type='submit']::-moz-focus-inner {
- border-style: none;
- padding: 0;
-}
-
-/**
- * Restore the focus styles unset by the previous rule.
- */
-
-button:-moz-focusring,
-[type='button']:-moz-focusring,
-[type='reset']:-moz-focusring,
-[type='submit']:-moz-focusring {
- outline: 1px dotted ButtonText;
+ -webkit-appearance: button; /* 1 */
+ /* background-color: transparent; */
+ background-image: none; /* 2 */
}
-/**
- * Correct the padding in Firefox.
- */
+/*
+Use the modern Firefox focus style for all focusable elements.
+*/
-fieldset {
- padding: 0.35em 0.75em 0.625em;
+:-moz-focusring {
+ outline: auto;
}
-/**
- * 1. Correct the text wrapping in Edge and IE.
- * 2. Correct the color inheritance from `fieldset` elements in IE.
- * 3. Remove the padding so developers are not caught out when they zero out
- * `fieldset` elements in all browsers.
- */
+/*
+Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
+*/
-legend {
- box-sizing: border-box;
- /* 1 */
- color: inherit;
- /* 2 */
- display: table;
- /* 1 */
- max-width: 100%;
- /* 1 */
- padding: 0;
- /* 3 */
- white-space: normal;
- /* 1 */
+:-moz-ui-invalid {
+ box-shadow: none;
}
-/**
- * Add the correct vertical alignment in Chrome, Firefox, and Opera.
- */
+/*
+Add the correct vertical alignment in Chrome and Firefox.
+*/
progress {
vertical-align: baseline;
}
-/**
- * Remove the default vertical scrollbar in IE 10+.
- */
-
-textarea {
- overflow: auto;
-}
-
-/**
- * 1. Add the correct box sizing in IE 10.
- * 2. Remove the padding in IE 10.
- */
-
-[type='checkbox'],
-[type='radio'] {
- box-sizing: border-box;
- /* 1 */
- padding: 0;
- /* 2 */
-}
-
-/**
- * Correct the cursor style of increment and decrement buttons in Chrome.
- */
+/*
+Correct the cursor style of increment and decrement buttons in Safari.
+*/
-[type='number']::-webkit-inner-spin-button,
-[type='number']::-webkit-outer-spin-button {
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
height: auto;
}
-/**
- * 1. Correct the odd appearance in Chrome and Safari.
- * 2. Correct the outline style in Safari.
- */
+/*
+1. Correct the odd appearance in Chrome and Safari.
+2. Correct the outline style in Safari.
+*/
[type='search'] {
- -webkit-appearance: textfield;
- /* 1 */
- outline-offset: -2px;
- /* 2 */
+ -webkit-appearance: textfield; /* 1 */
+ outline-offset: -2px; /* 2 */
}
-/**
- * Remove the inner padding in Chrome and Safari on macOS.
- */
+/*
+Remove the inner padding in Chrome and Safari on macOS.
+*/
-[type='search']::-webkit-search-decoration {
+::-webkit-search-decoration {
-webkit-appearance: none;
}
-/**
- * 1. Correct the inability to style clickable types in iOS and Safari.
- * 2. Change font properties to `inherit` in Safari.
- */
-
-::-webkit-file-upload-button {
- -webkit-appearance: button;
- /* 1 */
- font: inherit;
- /* 2 */
-}
-
-/* Interactive
- ========================================================================== */
-
/*
- * Add the correct display in Edge, IE 10+, and Firefox.
- */
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Change font properties to `inherit` in Safari.
+*/
-details {
- display: block;
+::-webkit-file-upload-button {
+ -webkit-appearance: button; /* 1 */
+ font: inherit; /* 2 */
}
/*
- * Add the correct display in all browsers.
- */
+Add the correct display in Chrome and Safari.
+*/
summary {
display: list-item;
}
-/* Misc
- ========================================================================== */
-
-/**
- * Add the correct display in IE 10+.
- */
-
-template {
- display: none;
-}
-
-/**
- * Add the correct display in IE 10.
- */
-
-[hidden] {
- display: none;
-}
+/*
+Removes the default spacing and border for appropriate elements.
+*/
-HTML,
-body,
-div,
-ul,
-ol,
+blockquote,
dl,
-li,
-dt,
dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
p,
-blockquote,
-pre,
-form,
-fieldset,
-table,
-th,
-td {
- // border: none;
- font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',
- 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
- font-size: 14px;
- margin: 0px;
- padding: 0px;
-}
-
-html,
-body {
- height: 100%;
- width: 100%;
-}
-
-address,
-caption,
-cite,
-code,
-th,
-var {
- font-style: normal;
- font-weight: normal;
-}
-
-a {
- text-decoration: none;
+pre {
+ margin: 0;
}
-input::-ms-clear {
- display: none;
+fieldset {
+ margin: 0;
+ padding: 0;
}
-input::-ms-reveal {
- display: none;
+legend {
+ padding: 0;
}
-input {
- -webkit-appearance: none;
+ol,
+ul,
+menu {
+ list-style: none;
margin: 0;
- outline: none;
padding: 0;
}
-input::-webkit-input-placeholder {
- color: #ccc;
-}
+/*
+Prevent resizing textareas horizontally by default.
+*/
-input::-ms-input-placeholder {
- color: #ccc;
+textarea {
+ resize: vertical;
}
-input::-moz-placeholder {
- color: #ccc;
+/*
+1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
+2. Set the default placeholder color to the user's configured gray 400 color.
+*/
+
+input::placeholder,
+textarea::placeholder {
+ opacity: 1; /* 1 */
+ color: #9ca3af; /* 2 */
}
-input[type='submit'],
-input[type='button'] {
+/*
+Set the default cursor for buttons.
+*/
+
+button,
+[role='button'] {
cursor: pointer;
}
-button[disabled],
-input[disabled] {
+/*
+Make sure disabled buttons don't get the pointer cursor.
+*/
+:disabled {
cursor: default;
}
-img {
- border: none;
+/*
+1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+ This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+ display: block; /* 1 */
+ vertical-align: middle; /* 2 */
}
-ul,
-ol,
-li {
- list-style-type: none;
+/*
+Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+
+img,
+video {
+ max-width: 100%;
+ height: auto;
+}
+
+/* Make elements with the HTML hidden attribute stay hidden by default */
+[hidden] {
+ display: none;
}
diff --git a/web/src/utils/dictionary.js b/web/src/utils/dictionary.js
index 89ec656e43..c67bb82811 100644
--- a/web/src/utils/dictionary.js
+++ b/web/src/utils/dictionary.js
@@ -1,9 +1,76 @@
import { useDictionaryStore } from '@/pinia/modules/dictionary'
-// 获取字典方法 使用示例 getDict('sex').then(res) 或者 async函数下 const res = await getDict('sex')
-export const getDict = async (type) => {
- const dictionaryStore = useDictionaryStore()
- await dictionaryStore.getDictionary(type)
- return dictionaryStore.dictionaryMap[type]
+
+/**
+ * 生成字典缓存key
+ * @param {string} type - 字典类型
+ * @param {number} depth - 深度参数
+ * @param {string|number|null} value - 指定节点的value
+ * @returns {string} 缓存key
+ */
+const generateCacheKey = (type, depth, value) => {
+ if (value !== null && value !== undefined) {
+ return `${type}_value_${value}_depth_${depth}`
+ }
+ return depth === 0 ? `${type}_tree` : `${type}_depth_${depth}`
+}
+
+/**
+ * 获取字典数据
+ * @param {string} type - 字典类型,必填
+ * @param {Object} options - 可选参数
+ * @param {number} options.depth - 指定获取字典的深度,默认为0(完整树形结构)
+ * @param {string|number|null} options.value - 指定节点的value,获取该节点的children,默认为null
+ * @returns {Promise
} 字典数据数组
+ * @example
+ * // 获取完整的字典树形结构
+ * const dictTree = await getDict('user_status')
+ *
+ * // 获取指定深度的扁平化字典数据
+ * const dictFlat = await getDict('user_status', {
+ * depth: 2
+ * })
+ *
+ * // 获取指定节点的children
+ * const children = await getDict('user_status', {
+ * value: 'active'
+ * })
+ */
+export const getDict = async (
+ type,
+ options = {
+ depth: 0,
+ value: null
+ }
+) => {
+ // 参数验证
+ if (!type || typeof type !== 'string') {
+ console.warn('getDict: type参数必须是非空字符串')
+ return []
+ }
+
+ if (typeof options.depth !== 'number' || options.depth < 0) {
+ console.warn('getDict: depth参数必须是非负数')
+ options.depth = 0
+ }
+
+ try {
+ const dictionaryStore = useDictionaryStore()
+
+ // 调用store方法获取字典数据
+ await dictionaryStore.getDictionary(type, options.depth, options.value)
+
+ // 生成缓存key
+ const cacheKey = generateCacheKey(type, options.depth, options.value)
+
+ // 从缓存中获取数据
+ const result = dictionaryStore.dictionaryMap[cacheKey]
+
+ // 返回数据,确保返回数组
+ return Array.isArray(result) ? result : []
+ } catch (error) {
+ console.error('getDict: 获取字典数据失败', { type, options, error })
+ return []
+ }
}
// 字典文字展示方法
diff --git a/web/src/utils/format.js b/web/src/utils/format.js
index f5062e762d..dc5cf07f48 100644
--- a/web/src/utils/format.js
+++ b/web/src/utils/format.js
@@ -19,18 +19,57 @@ export const formatDate = (time) => {
}
export const filterDict = (value, options) => {
- const rowLabel = options && options.filter((item) => item.value === value)
- return rowLabel && rowLabel[0] && rowLabel[0].label
+ // 递归查找函数
+ const findInOptions = (opts, targetValue) => {
+ if (!opts || !Array.isArray(opts)) return null
+
+ for (const item of opts) {
+ if (item.value === targetValue) {
+ return item
+ }
+
+ if (item.children && Array.isArray(item.children)) {
+ const found = findInOptions(item.children, targetValue)
+ if (found) return found
+ }
+ }
+
+ return null
+ }
+
+ const rowLabel = findInOptions(options, value)
+ return rowLabel && rowLabel.label
}
export const filterDataSource = (dataSource, value) => {
+ // 递归查找函数
+ const findInDataSource = (data, targetValue) => {
+ if (!data || !Array.isArray(data)) return null
+
+ for (const item of data) {
+ // 检查当前项是否匹配
+ if (item.value === targetValue) {
+ return item
+ }
+
+ // 如果有children属性,递归查找
+ if (item.children && Array.isArray(item.children)) {
+ const found = findInDataSource(item.children, targetValue)
+ if (found) return found
+ }
+ }
+
+ return null
+ }
+
if (Array.isArray(value)) {
return value.map((item) => {
- const rowLabel = dataSource && dataSource.find((i) => i.value === item)
+ const rowLabel = findInDataSource(dataSource, item)
return rowLabel?.label
})
}
- const rowLabel = dataSource && dataSource.find((item) => item.value === value)
+
+ const rowLabel = findInDataSource(dataSource, value)
return rowLabel?.label
}
diff --git a/web/src/utils/request.js b/web/src/utils/request.js
index 1c62bd311b..9dbc8d46fb 100644
--- a/web/src/utils/request.js
+++ b/web/src/utils/request.js
@@ -124,7 +124,8 @@ service.interceptors.request.use(
)
function getErrorMessage(error) {
- return error.response?.data?.msg || '请求失败'
+ // 优先级: 响应体中的 msg > statusText > 默认消息
+ return error.response?.data?.msg || error.response?.statusText || '请求失败'
}
// http response 拦截器
diff --git a/web/src/view/example/upload/upload.vue b/web/src/view/example/upload/upload.vue
index eae676a0eb..fdc86825ab 100644
--- a/web/src/view/example/upload/upload.vue
+++ b/web/src/view/example/upload/upload.vue
@@ -1,26 +1,46 @@
-
-
+
+
- {{ data.name }}
+
+ {{ data.name }}
-
+
- 添加分类
- 编辑分类
- 删除分类
+ 添加分类
+ 编辑分类
+ 删除分类
@@ -28,98 +48,118 @@
-
+
-
+
-
+
导入URL
查询
-
+ >查询
+
-
+
-
+
{{ formatDate(scope.row.UpdatedAt) }}
-
+
{{ scope.row.name }}
-
+
{{ scope.row.tag }}
+ :type="
+ scope.row.tag?.toLowerCase() === 'jpg' ? 'info' : 'success'
+ "
+ disable-transitions
+ >{{ scope.row.tag }}
下载
-
+ icon="download"
+ type="primary"
+ link
+ @click="downloadFile(scope.row)"
+ >下载
+
删除
-
+ icon="delete"
+ type="primary"
+ link
+ @click="deleteFileFunc(scope.row)"
+ >删除
+
@@ -127,23 +167,34 @@
-
-
+
-
+
@@ -151,88 +202,91 @@
确定
-
diff --git a/web/src/view/layout/header/index.vue b/web/src/view/layout/header/index.vue
index bf7b5be1c5..52631bd08f 100644
--- a/web/src/view/layout/header/index.vue
+++ b/web/src/view/layout/header/index.vue
@@ -9,15 +9,11 @@
>
-
![]()
+
-
+
@@ -34,7 +35,10 @@
id="gva-base-load-dom"
class="gva-body-h bg-gray-50 dark:bg-slate-800"
>
-
+
diff --git a/web/src/view/layout/setting/modules/general/index.vue b/web/src/view/layout/setting/modules/general/index.vue
index 31e2a85006..e23fc24ea4 100644
--- a/web/src/view/layout/setting/modules/general/index.vue
+++ b/web/src/view/layout/setting/modules/general/index.vue
@@ -49,9 +49,11 @@
-
+
-
+
🔄
@@ -59,19 +61,18 @@
将所有设置恢复为默认值
-
+ @click="handleResetConfig">
重置配置
-
+
-
+
📤
@@ -79,20 +80,19 @@
导出当前配置为 JSON 文件
-
+ @click="handleExportConfig">
导出配置
-
+
-
+
📥
@@ -100,18 +100,10 @@
从 JSON 文件导入配置
-
-
+
+
导入配置
@@ -131,13 +123,9 @@
-
-

+
+
Gin-Vue-Admin
@@ -145,21 +133,15 @@
基于 Vue3 + Gin 的全栈开发基础平台,提供完整的后台管理解决方案
@@ -172,10 +154,11 @@
diff --git a/web/src/view/superAdmin/dictionary/sysDictionaryDetail.vue b/web/src/view/superAdmin/dictionary/sysDictionaryDetail.vue
index a03229f374..66fc538fa3 100644
--- a/web/src/view/superAdmin/dictionary/sysDictionaryDetail.vue
+++ b/web/src/view/superAdmin/dictionary/sysDictionaryDetail.vue
@@ -1,31 +1,56 @@
-
+
字典详细内容
-
- 新增字典项
-
+
+
+
+ 搜索
+
+
+
+ 新增字典项
+
+
+
+
+
+
+
+
+
+
{{ formatDate(scope.row.CreatedAt) }}
-
-
-
-
-
+
-
+
+
+ 添加子项
+
-
-
+
+
+
{
- pageSize.value = val
- getTableData()
+ // 级联选择器配置
+ const cascadeProps = {
+ value: 'ID',
+ label: 'label',
+ children: 'children',
+ checkStrictly: true, // 允许选择任意级别
+ emitPath: false // 只返回选中节点的值
}
- const handleCurrentChange = (val) => {
- page.value = val
- getTableData()
- }
- // 查询
- const getTableData = async () => {
+ // 获取树形数据
+ const getTreeData = async () => {
if (!props.sysDictionaryID) return
- const table = await getSysDictionaryDetailList({
- page: page.value,
- pageSize: pageSize.value,
- sysDictionaryID: props.sysDictionaryID
- })
- if (table.code === 0) {
- tableData.value = table.data.list
- total.value = table.data.total
- page.value = table.data.page
- pageSize.value = table.data.pageSize
+ try {
+ const res = await getDictionaryTreeList({
+ sysDictionaryID: props.sysDictionaryID
+ })
+ if (res.code === 0) {
+ treeData.value = res.data.list || []
+ }
+ } catch (error) {
+ console.error('获取树形数据失败:', error)
+ ElMessage.error('获取层级数据失败')
}
}
- getTableData()
+ const rootOption = {
+ ID: null,
+ label: '无父级(根级)'
+ }
+
+
+ // 初始加载
+ getTreeData()
const type = ref('')
const drawerFormVisible = ref(false)
+
const updateSysDictionaryDetailFunc = async (row) => {
drawerForm.value && drawerForm.value.clearValidate()
const res = await findSysDictionaryDetail({ ID: row.ID })
@@ -247,6 +292,27 @@
}
}
+ // 添加子节点
+ const addChildNode = (parentNode) => {
+ console.log(parentNode)
+ type.value = 'create'
+ formData.value = {
+ label: null,
+ value: null,
+ status: true,
+ sort: null,
+ parentID: parentNode.ID,
+ sysDictionaryID: props.sysDictionaryID
+ }
+ drawerForm.value && drawerForm.value.clearValidate()
+ drawerFormVisible.value = true
+ }
+
+ // 处理父级选择变化
+ const handleParentChange = (value) => {
+ formData.value.parentID = value
+ }
+
const closeDrawer = () => {
drawerFormVisible.value = false
formData.value = {
@@ -254,9 +320,11 @@
value: null,
status: true,
sort: null,
+ parentID: null,
sysDictionaryID: props.sysDictionaryID
}
}
+
const deleteSysDictionaryDetailFunc = async (row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
@@ -272,7 +340,7 @@
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
- getTableData()
+ await getTreeData() // 重新加载数据
}
})
}
@@ -300,22 +368,41 @@
message: '创建/更改成功'
})
closeDrawer()
- getTableData()
+ await getTreeData() // 重新加载数据
}
})
}
+
const openDrawer = () => {
type.value = 'create'
+ formData.value.parentID = null
drawerForm.value && drawerForm.value.clearValidate()
drawerFormVisible.value = true
}
+ const clearSearchInput = () => {
+ searchName.value = ''
+ getTreeData()
+ }
+
+ const handleCloseSearchInput = () => {
+ // 处理搜索输入框关闭
+ }
+
+ const handleInputKeyDown = (e) => {
+ if (e.key === 'Enter' && searchName.value.trim() !== '') {
+ getTreeData()
+ }
+ }
+
watch(
() => props.sysDictionaryID,
() => {
- getTableData()
+ getTreeData()
}
)
-
+
diff --git a/web/src/view/systemTools/system/system.vue b/web/src/view/systemTools/system/system.vue
index 8411518c91..1b076383cf 100644
--- a/web/src/view/systemTools/system/system.vue
+++ b/web/src/view/systemTools/system/system.vue
@@ -47,6 +47,9 @@
+
+
+