diff --git a/README.md b/README.md index 963085f..8787051 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,158 @@ -# vueadmin -This template should help get you started developing with Vue 3 in Vite. +## 功能特性 -## Recommended IDE Setup +### 1. 权限管理 +- 基于角色的权限控制 (RBAC) +- 动态路由生成 +- 菜单权限过滤 +- 按钮级权限控制 -[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). +### 2. 布局系统 +- 响应式布局设计 +- 顶部导航栏 +- 侧边菜单栏 +- 面包屑导航 +- 标签页导航 +- 设置面板 -## Recommended Browser Setup +### 3. 国际化支持 +- 多语言切换 (中文/英文) +- 动态加载语言包 +- 支持自定义语言扩展 -- Chromium-based browsers (Chrome, Edge, Brave, etc.): - - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) - - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) -- Firefox: - - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) - - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) +### 4. 组件库 +- 自定义表格组件 (scTable) +- 自定义表单组件 (scForm) +- 自定义上传组件 (scUpload) +- 其他常用业务组件 -## Customize configuration +### 5. 系统功能 +- 用户登录/注册/重置密码 +- 仪表盘 +- 系统设置 +- 用户管理 +- 角色管理 +- 菜单管理 +- 区域管理 -See [Vite Configuration Reference](https://vite.dev/config/). +## 安装和运行 -## Project Setup +### 环境要求 +- Node.js >= 20.19.0 || >= 22.12.0 -```sh -yarn +### 安装依赖 + +```bash +npm install ``` -### Compile and Hot-Reload for Development +### 开发模式 -```sh -yarn dev +```bash +npm run dev ``` -### Compile and Minify for Production +项目将在 `http://localhost:5173` 启动。 -```sh -yarn build +### 构建生产版本 + +```bash +npm run build ``` -### Lint with [ESLint](https://eslint.org/) +构建后的文件将输出到 `dist` 目录。 -```sh -yarn lint +### 预览生产版本 + +```bash +npm run preview ``` + +### 代码检查和格式化 + +```bash +# ESLint 检查并修复 +npm run lint + +# Prettier 格式化代码 +npm run format +``` + +## 配置说明 + +项目的主要配置文件位于 `src/config/index.js`,包含以下配置项: + +```javascript +{ + APP_NAME: 'vueadmin', // 应用名称 + DASHBOARD_URL: '/dashboard', // 仪表盘URL + API_URL: 'https://www.tensent.cn/admin/', // API接口地址 + TIMEOUT: 50000, // 请求超时时间 + TOKEN_NAME: 'authorization', // Token名称 + TOKEN_PREFIX: 'Bearer ', // Token前缀 + LANG: 'zh-cn', // 默认语言 + // 更多配置... +} +``` + +## 开发指南 + +### 路由配置 + +路由配置分为两部分: +1. 系统基础路由 (`src/router/systemRoutes.js`) +2. 动态生成的业务路由 (由后端菜单数据转换) + +### 组件开发 + +自定义组件位于 `src/components/` 目录,每个组件有独立的文件夹,包含组件文件和相关资源。 + +### API 调用 + +API 接口定义位于 `src/api/` 目录,使用 Axios 封装,支持拦截器、请求缓存等功能。 + +### 状态管理 + +使用 Pinia 进行状态管理,store 定义位于 `src/stores/` 目录,支持模块化管理。 + +## 国际化 + +国际化配置位于 `src/i18n/` 目录,支持中英文切换,可通过以下方式扩展其他语言: + +1. 在 `src/i18n/locales/` 目录下添加新的语言文件 +2. 在 `src/i18n/index.js` 中注册新语言 + +## 权限管理 + +系统采用基于角色的权限控制 (RBAC),权限信息由后端提供,前端根据权限信息动态生成路由和菜单。 + +## 浏览器支持 + +- Chrome (最新版本) +- Firefox (最新版本) +- Safari (最新版本) +- Edge (最新版本) + +## 注意事项 + +1. 确保 Node.js 版本符合要求 +2. 开发环境下 API 请求可能需要配置代理 +3. 生产环境需要配置正确的 API 地址 +4. 首次运行需要先安装依赖 + +## License + +MIT License + +## 更新日志 + +### v1.6.6 +- 升级 Vue 3 到 3.5.26 +- 升级 Vite 到 7.3.0 +- 升级 Ant Design Vue 到 4.2.6 +- 优化路由管理和权限控制 +- 修复已知 bug + +### 贡献 + +欢迎提交 Issue 和 Pull Request 来帮助改进这个项目! \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 13eac6f..a9755bb 100644 --- a/src/App.vue +++ b/src/App.vue @@ -66,21 +66,3 @@ onMounted(() => { - - diff --git a/src/api/auth.js b/src/api/auth.js index 0e34f7b..97767af 100644 --- a/src/api/auth.js +++ b/src/api/auth.js @@ -1,34 +1,34 @@ -import request from '../utils/request' +import request from '@/utils/request' -/** - * 用户登录 - * @returns {Promise} 菜单数据 - */ -export function userLogin(params) { - return request({ - url: '/auth/login', - method: 'post', - data: params - }) -} - -export function userLogout() { - return request({ - url: '/auth/logout', - method: 'get' - }) -} - -export function getUserInfo() { - return request({ - url: '/auth/user', - method: 'get' - }) -} - -export function getMyMenu(){ - return request({ - url: `auth/menu/my`, - method: 'get' - }) +export default { + login: { + url: `auth/login`, + name: '用户登录', + post: async function (params) { + return await request.post(this.url, params) + }, + }, + logout: { + url: `auth/logout`, + name: '用户登出', + get: async function () { + return await request.get(this.url) + }, + }, + user: { + url: `auth/user`, + name: '获取用户信息', + get: async function () { + return await request.get(this.url) + }, + }, + menu: { + my: { + url: `auth/menu/my`, + name: '获取我的菜单', + get: async function () { + return await request.get(this.url) + }, + }, + }, } diff --git a/src/api/system.js b/src/api/system.js index 1abc194..4e7bd73 100644 --- a/src/api/system.js +++ b/src/api/system.js @@ -18,8 +18,8 @@ export default { info: { url: `system/index/info`, name: '系统信息', - get: function (data) { - return request.get(this.url, data) + get: function (params) { + return request.get(this.url, { params }) }, }, setting: { @@ -27,14 +27,14 @@ export default { url: `system/setting/index`, name: '获取配置信息', get: function (params) { - return request.get(this.url, params) + return request.get(this.url, { params }) }, }, fields: { url: `system/setting/fields`, name: '获取配置字段', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, add: { @@ -64,7 +64,7 @@ export default { url: `system/dict/category`, name: '获取字典树', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, editcate: { @@ -92,14 +92,14 @@ export default { url: `system/dict/lists`, name: '字典明细', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, get: { url: `system/dict/detail`, name: '获取字典数据', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, edit: { @@ -127,14 +127,14 @@ export default { url: `system/dict/detail`, name: '字典明细', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, alldic: { url: `system/dict/all`, name: '全部字典', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, }, @@ -175,7 +175,7 @@ export default { url: `system/client/index`, name: '客户端列表', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, add: { @@ -204,7 +204,7 @@ export default { url: `system/menu/index`, name: '客户端菜单列表', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, add: { @@ -235,14 +235,14 @@ export default { url: `system/log/index`, name: '日志列表', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, my: { url: `system/log/my`, name: '我的日志', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, delete: { @@ -258,7 +258,7 @@ export default { url: `system/tasks/index`, name: '任务列表', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, delete: { @@ -274,7 +274,7 @@ export default { url: `system/crontab/index`, name: '定时任务列表', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, add: { @@ -302,7 +302,7 @@ export default { url: `system/crontab/log`, name: '定时任务日志', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, reload: { @@ -318,7 +318,7 @@ export default { url: `system/modules/index`, name: '模块列表', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, update: { @@ -334,7 +334,7 @@ export default { url: `system/sms/count`, name: '短信发送统计', get: async function (params) { - return await request.get(this.url, params) + return await request.get(this.url, { params }) }, }, }, diff --git a/src/assets/style/app.scss b/src/assets/style/app.scss new file mode 100644 index 0000000..1eb3915 --- /dev/null +++ b/src/assets/style/app.scss @@ -0,0 +1,29 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#app { + min-height: 100vh; +} + +.pages{ + flex: 1; + display: flex; + flex-direction: column; + background-color: #ffffff; + padding: 10px; + border-radius: 10px; + .search-box{ + padding: 10px; + background-color: #f5f5f5; + border-radius: 10px; + } +} diff --git a/src/components/scTable/index.vue b/src/components/scTable/index.vue index e7ad37a..7c7d21e 100644 --- a/src/components/scTable/index.vue +++ b/src/components/scTable/index.vue @@ -256,18 +256,22 @@ const tableContent = useTemplateRef('tableContent') let scroll = ref({ scrollToFirstRowOnChange: true, x: 'max-content', - y: 100, + y: true, }) onMounted(() => { - let tableHeight = 100 + updateTableHeight() +}) + +const updateTableHeight = () => { + let tableHeight = 0 if (props.pagination !== false) { - tableHeight = tableContent.value.clientHeight - 100 + tableHeight = tableContent.value.clientHeight - 105 } else { tableHeight = tableContent.value.clientHeight - 65 } scroll.value.y = tableHeight -}) +} // 根据表格宽度优化横向滚动配置 watch( diff --git a/src/layouts/index.vue b/src/layouts/index.vue index aa6d9c6..ab70c7b 100644 --- a/src/layouts/index.vue +++ b/src/layouts/index.vue @@ -344,6 +344,8 @@ onMounted(() => { overflow-y: auto; flex: 1; height: calc(100vh - 106px); + display: flex; + flex-direction: column; } /* 默认布局 - 双栏菜单 */ diff --git a/src/main.js b/src/main.js index f36103c..e70ebe6 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,7 @@ import { createApp } from 'vue' import Antd from 'ant-design-vue' import 'ant-design-vue/dist/reset.css' +import '@/assets/style/app.scss' import App from './App.vue' import router from './router' import pinia from './stores' diff --git a/src/pages/auth/department/index.vue b/src/pages/auth/department/index.vue new file mode 100644 index 0000000..f33e786 --- /dev/null +++ b/src/pages/auth/department/index.vue @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 查看 + + 编辑 + + + + 删除 + + + + + + + + + + + + + + + diff --git a/src/pages/auth/department/save.vue b/src/pages/auth/department/save.vue new file mode 100644 index 0000000..2595d5c --- /dev/null +++ b/src/pages/auth/department/save.vue @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + 取 消 + 保 存 + + + + + diff --git a/src/pages/auth/permission/index.vue b/src/pages/auth/permission/index.vue new file mode 100644 index 0000000..acd266d --- /dev/null +++ b/src/pages/auth/permission/index.vue @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + {{ node.label }} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/auth/permission/save.vue b/src/pages/auth/permission/save.vue new file mode 100644 index 0000000..9e4cd97 --- /dev/null +++ b/src/pages/auth/permission/save.vue @@ -0,0 +1,209 @@ + + + + + + + + {{form.title || "新增菜单"}} + + + + + + + + + + 菜单 + Iframe + 外链 + 按钮 + + + + + 系统唯一且与内置组件名一致,否则导致缓存失效。如类型为Iframe的菜单,别名将代替源地址显示在地址栏 + + + + + + + + + + + + + + + + 子节点或详情页需要高亮的上级菜单路由地址 + + + + pages/ + + 如父节点、链接或Iframe等没有视图的菜单不需要填写 + + + + + + 隐藏菜单 + 隐藏面包屑 + 菜单不显示在导航中,但用户依然可以访问,例如详情页 + + + + 是否固定,类似首页控制台在标签中是没有关闭按钮的 + + + + 是否全屏 + + + 保 存 + + + + + + 接口权限 + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/auth/role/index.vue b/src/pages/auth/role/index.vue new file mode 100644 index 0000000..f384467 --- /dev/null +++ b/src/pages/auth/role/index.vue @@ -0,0 +1,173 @@ + + + + + + + 权限设置 + + + + + + + + + + + + + + + + + {{ scope.row.status == 1 ? '正常' : '禁用' }} + + + + + + 查看 + 编辑 + + + 删除 + + + + + + + + + + + + + + + + + diff --git a/src/pages/auth/role/permission.vue b/src/pages/auth/role/permission.vue new file mode 100644 index 0000000..f8d3c0e --- /dev/null +++ b/src/pages/auth/role/permission.vue @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + {{ item.label }} + {{ item.views }} + + + 用于控制角色登录后控制台的视图 + + + + + + 取 消 + 保 存 + + + + + + + diff --git a/src/pages/auth/role/save.vue b/src/pages/auth/role/save.vue new file mode 100644 index 0000000..fa68871 --- /dev/null +++ b/src/pages/auth/role/save.vue @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + 取 消 + 保 存 + + + + + + + diff --git a/src/pages/auth/user/index.vue b/src/pages/auth/user/index.vue index 8e3cfb6..c9deb3b 100644 --- a/src/pages/auth/user/index.vue +++ b/src/pages/auth/user/index.vue @@ -1,180 +1,218 @@ - - - - - - 新增用户 - - + + + + + + + + + + + + + + + + + 分配角色 + + + + + + + + + + + + + + + + {{ scope.row.username }} + {{ scope.row.nickname }} + + + + + {{item.title}} + + + {{scope.row.department?.title}} + + + + 查看 + 编辑 + + + 删除 + + + + + + + + - - - + + - - - - {{ record.status === 1 ? '正常' : '禁用' }} - - - - - 编辑 - 删除 - - - - - - - diff --git a/src/pages/auth/user/role.vue b/src/pages/auth/user/role.vue new file mode 100644 index 0000000..5d2ee3a --- /dev/null +++ b/src/pages/auth/user/role.vue @@ -0,0 +1,78 @@ + + + + + + + + + + + 取 消 + 保 存 + + + + + + + diff --git a/src/pages/auth/user/save.vue b/src/pages/auth/user/save.vue new file mode 100644 index 0000000..362dca0 --- /dev/null +++ b/src/pages/auth/user/save.vue @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取 消 + 保 存 + + + + + + + diff --git a/src/pages/login/index.vue b/src/pages/login/index.vue index c96626b..5516ce2 100644 --- a/src/pages/login/index.vue +++ b/src/pages/login/index.vue @@ -51,7 +51,7 @@ import { useRouter } from 'vue-router' import { message } from 'ant-design-vue' import { UserOutlined, LockOutlined } from '@ant-design/icons-vue' import { useUserStore } from '@/stores/modules/user' -import { userLogin, getUserInfo, getMyMenu } from '@/api/auth' +import authApi from '@/api/auth' // 定义组件名称(多词命名) defineOptions({ @@ -72,14 +72,14 @@ const handleLogin = async () => { loading.value = true; try { - let res = await userLogin({ username: formState.username, password: formState.password }) + let res = await authApi.login.post({ username: formState.username, password: formState.password }) if (res.code == 1) { userStore.setToken(res.data.access_token) - let userInfo = await getUserInfo() + let userInfo = await authApi.user.get() if (userInfo.code == 1) { userStore.setUserInfo(userInfo.data) } - let authData = await getMyMenu() + let authData = await authApi.menu.my.get() if (authData.code == 1){ userStore.setMenu(authData.data.menu) userStore.setPermissions(authData.data.permissions) diff --git a/src/pages/system/area/index.vue b/src/pages/system/area/index.vue index 6f3895f..04dc3cc 100644 --- a/src/pages/system/area/index.vue +++ b/src/pages/system/area/index.vue @@ -1,5 +1,5 @@ - + @@ -160,7 +160,6 @@ const loadData = async () => { const res = await systemApi.area.list.get(params) if (res.code === 1) { dataSource.value = res.data.list || res.data || [] - pagination.total = res.data.total || 0 } } catch (error) { message.error(t('common.fetchDataFailed')) @@ -274,15 +273,3 @@ onMounted(() => { loadData() }) - - diff --git a/src/pages/system/client/index.vue b/src/pages/system/client/index.vue new file mode 100644 index 0000000..fe86f98 --- /dev/null +++ b/src/pages/system/client/index.vue @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + 编辑 + 查看 + 菜单 + + + 删除 + + + + + + + + + + + + + + diff --git a/src/pages/system/client/menu.vue b/src/pages/system/client/menu.vue new file mode 100644 index 0000000..11ca92a --- /dev/null +++ b/src/pages/system/client/menu.vue @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + {{ scope.row.client?.title }} + + + + 编辑 + 查看 + + + 删除 + + + + + + + + + + + + + diff --git a/src/pages/system/client/menuform.vue b/src/pages/system/client/menuform.vue new file mode 100644 index 0000000..50f59d5 --- /dev/null +++ b/src/pages/system/client/menuform.vue @@ -0,0 +1,76 @@ + + + + + + + + + + + diff --git a/src/pages/system/client/save.vue b/src/pages/system/client/save.vue new file mode 100644 index 0000000..7612961 --- /dev/null +++ b/src/pages/system/client/save.vue @@ -0,0 +1,69 @@ + + + + + + + + + + + diff --git a/src/pages/system/crontab/index.vue b/src/pages/system/crontab/index.vue new file mode 100644 index 0000000..50933d2 --- /dev/null +++ b/src/pages/system/crontab/index.vue @@ -0,0 +1,154 @@ + + + + + + {{item.title}} + + + 任务类型 + {{crontabType[item.type]}} + + + 执行类 + {{item.command}} + + + 定时规则 + {{item.expression}} + + + + + 运行中 + 停用 + + + + + + + + + + + + 编辑 + 日志 + 删除 + + + + + + + + + + + 添加计划任务 + + + + + + + + + + + + diff --git a/src/pages/system/crontab/logs.vue b/src/pages/system/crontab/logs.vue new file mode 100644 index 0000000..d5396a7 --- /dev/null +++ b/src/pages/system/crontab/logs.vue @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + 日志 + + + + + + + + + + + {{logDetail}} + + + + + + + + diff --git a/src/pages/system/crontab/save.vue b/src/pages/system/crontab/save.vue new file mode 100644 index 0000000..924328f --- /dev/null +++ b/src/pages/system/crontab/save.vue @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + 取 消 + 保 存 + + + + + + + diff --git a/src/pages/system/dic/dic.vue b/src/pages/system/dic/dic.vue new file mode 100644 index 0000000..0a1f059 --- /dev/null +++ b/src/pages/system/dic/dic.vue @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + 取 消 + 保 存 + + + + + + + diff --git a/src/pages/system/dic/index.vue b/src/pages/system/dic/index.vue new file mode 100644 index 0000000..15bb325 --- /dev/null +++ b/src/pages/system/dic/index.vue @@ -0,0 +1,321 @@ + + + + + + + + + + + + {{ node.label }} + {{ data.name }} + + + + + + + + + + 字典分类 + + + + + + + + + + + + + + + + + + + + + + + 是 + 否 + + + + + + + 编辑 + + + 删除 + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/system/dic/list.vue b/src/pages/system/dic/list.vue new file mode 100644 index 0000000..dde31b6 --- /dev/null +++ b/src/pages/system/dic/list.vue @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + 取 消 + 保 存 + + + + + + + \ No newline at end of file diff --git a/src/pages/system/log/index.vue b/src/pages/system/log/index.vue new file mode 100644 index 0000000..51b3e57 --- /dev/null +++ b/src/pages/system/log/index.vue @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/pages/system/log/info.vue b/src/pages/system/log/info.vue new file mode 100644 index 0000000..7aa12da --- /dev/null +++ b/src/pages/system/log/info.vue @@ -0,0 +1,49 @@ + + + + + {{data.user?.nickname}} + {{data.client_ip}} + {{data.url}} + {{data.method}} + {{data.status}} + {{data.title}} + {{data.created_at}} + + + + {{data.data}} + + + + {{data.browser}} + + + + + + + + + diff --git a/src/pages/system/menu/index.vue b/src/pages/system/menu/index.vue deleted file mode 100644 index aecb3da..0000000 --- a/src/pages/system/menu/index.vue +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - 新增菜单 - - - - - - - - - - - {{ getTypeText(record.type) }} - - - - - {{ record.status === 1 ? '正常' : '禁用' }} - - - - - 编辑 - 新增子菜单 - 删除 - - - - - - - - - - - diff --git a/src/pages/system/modules/index.vue b/src/pages/system/modules/index.vue new file mode 100644 index 0000000..f482cfc --- /dev/null +++ b/src/pages/system/modules/index.vue @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + {{ scope.row.status == 1 ? '启用' : '禁用' }} + + + 更新 + + + + + + + + + diff --git a/src/pages/system/role/index.vue b/src/pages/system/role/index.vue deleted file mode 100644 index b9a47f0..0000000 --- a/src/pages/system/role/index.vue +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - 新增角色 - - - - - - - - {{ record.status === 1 ? '正常' : '禁用' }} - - - - - 编辑 - 权限 - 删除 - - - - - - - - - - - diff --git a/src/pages/system/setting/components/ConfigModal.vue b/src/pages/system/setting/components/ConfigModal.vue index c489005..d1f5345 100644 --- a/src/pages/system/setting/components/ConfigModal.vue +++ b/src/pages/system/setting/components/ConfigModal.vue @@ -25,14 +25,16 @@ - {{ $t('common.typeText') }} - {{ $t('common.typeTextarea') }} - {{ $t('common.typeNumber') }} - {{ $t('common.typeSwitch') }} - {{ $t('common.typeSelect') }} - {{ $t('common.typeMultiselect') }} - {{ $t('common.typeDatetime') }} - {{ $t('common.typeColor') }} + 文本 + 文本域 + 数字 + 开关 + 下拉选择 + 单选 + 多选 + 日期时间 + 颜色 + 图片 @@ -40,8 +42,8 @@ - - + + @@ -89,9 +91,9 @@ const formData = reactive({ category: '', name: '', title: '', - type: 'text', + type: 'string', value: '', - tip: '', + remark: '', }) // 监听弹窗显示和初始数据变化 @@ -100,9 +102,9 @@ watch(() => props.initialData, (newVal) => { formData.category = newVal.category || '' formData.name = newVal.name || '' formData.title = newVal.title || '' - formData.type = newVal.type || 'text' + formData.type = newVal.type || 'string' formData.value = newVal.value || '' - formData.tip = newVal.tip || '' + formData.remark = newVal.remark || newVal.tip || '' } }, { immediate: true }) @@ -129,9 +131,9 @@ const handleClose = () => { formData.category = '' formData.name = '' formData.title = '' - formData.type = 'text' + formData.type = 'string' formData.value = '' - formData.tip = '' + formData.remark = '' emit('update:visible', false) } diff --git a/src/pages/system/setting/index.vue b/src/pages/system/setting/index.vue index c0c27aa..28e281b 100644 --- a/src/pages/system/setting/index.vue +++ b/src/pages/system/setting/index.vue @@ -1,102 +1,102 @@ - - - - - - {{ $t('common.systemSettings') }} - + + + + + + + + {{ $t('common.addConfig') }} + - - - - - - {{ $t('common.addConfig') }} - - - - - - - - - - - - - - - - - - - - {{ option.label }} - - - - - - {{ option.label }} - - - - - - - - - - - + + + + + + + + + + + + + + + + + {{ option.label }} + + + + + + {{ option.label }} + + + + + + + + + + + + {{ $t('common.uploadImage') }} + + + - {{ field.tip }} - - + + + + + {{ field.tip }} + + - - - - + + + + - - - - - {{ $t('common.reset') }} - - - - {{ $t('common.save') }} - - - - + + + + + {{ $t('common.reset') }} + + + + {{ $t('common.save') }} + + + import { ref, reactive, onMounted } from 'vue' import { message } from 'ant-design-vue' -import { SettingOutlined, PlusOutlined, EditOutlined, SaveOutlined } from '@ant-design/icons-vue' +import { SettingOutlined, PlusOutlined, EditOutlined, SaveOutlined, UploadOutlined } from '@ant-design/icons-vue' import { useI18n } from 'vue-i18n' import systemApi from '@/api/system' import ConfigModal from './components/ConfigModal.vue' @@ -146,9 +146,43 @@ const currentEditData = ref({}) const fetchFields = async () => { try { const res = await systemApi.setting.fields.get() - if (res.code === 200) { - fields.value = res.data.fields || [] - categories.value = res.data.categories || categories.value + if (res.code === 1) { + // 根据返回的数据提取配置项 + const configData = res.data || [] + + // 根据 group 字段提取分类 + const groupMap = new Map() + configData.forEach(item => { + if (item.group && !groupMap.has(item.group)) { + // 将 group 名称转换为中文标题 + const groupTitles = { + 'base': '基础设置', + 'upload': '上传设置', + 'email': '邮件设置', + 'sms': '短信设置', + 'security': '安全设置', + } + groupMap.set(item.group, { + name: item.group, + title: groupTitles[item.group] || item.group + }) + } + }) + categories.value = Array.from(groupMap.values()) + + // 将配置项转换为前端需要的格式 + fields.value = configData.map(item => ({ + id: item.id, + name: item.name, + title: item.title || item.label, + value: item.values, + type: mapFieldType(item.type), + category: item.group, + placeholder: item.options?.placeholder || item.remark, + tip: item.remark, + options: mapFieldOptions(item.type, item.options?.options || []), + sort: item.sort + })) // 初始化表单数据 fields.value.forEach(field => { @@ -166,6 +200,39 @@ const fetchFields = async () => { } } +// 映射字段类型 +const mapFieldType = (backendType) => { + const typeMap = { + 'string': 'text', + 'text': 'text', + 'textarea': 'textarea', + 'number': 'number', + 'boolean': 'switch', + 'switch': 'switch', + 'select': 'select', + 'radio': 'select', + 'multiselect': 'multiselect', + 'checkbox': 'multiselect', + 'datetime': 'datetime', + 'date': 'datetime', + 'color': 'color', + 'image': 'image', + 'file': 'file', + } + return typeMap[backendType] || 'text' +} + +// 映射字段选项 +const mapFieldOptions = (type, options) => { + if (options && options.length > 0) { + return options.map(opt => ({ + label: opt.label || opt.name || opt, + value: opt.value || opt.key || opt + })) + } + return [] +} + // 切换 Tab const handleTabChange = (key) => { activeTab.value = key @@ -189,12 +256,16 @@ const handleAddConfig = () => { const handleEditField = (field) => { isEditMode.value = true currentEditData.value = { + id: field.id, category: field.category, name: field.name, title: field.title, type: field.type, value: formData[field.name], tip: field.tip || '', + remark: field.tip || '', + placeholder: field.placeholder, + options: field.options || [] } modalVisible.value = true } @@ -206,10 +277,11 @@ const handleModalConfirm = async (values) => { if (isEditMode.value) { // 编辑模式 res = await systemApi.setting.edit.post({ + id: currentEditData.value.id, ...values, name: currentEditData.value.name, }) - if (res.code === 200) { + if (res.code === 1) { message.success(t('common.editSuccess')) modalVisible.value = false @@ -221,13 +293,14 @@ const handleModalConfirm = async (values) => { if (fieldIndex > -1) { fields.value[fieldIndex].title = values.title fields.value[fieldIndex].value = values.value - fields.value[fieldIndex].tip = values.tip + fields.value[fieldIndex].tip = values.tip || values.remark + fields.value[fieldIndex].placeholder = values.placeholder } } } else { // 添加模式 res = await systemApi.setting.add.post(values) - if (res.code === 200) { + if (res.code === 1) { message.success(t('common.addSuccess')) modalVisible.value = false // 重新获取配置字段 @@ -248,7 +321,7 @@ const handleSave = async () => { try { saving.value = true const res = await systemApi.setting.save.post(formData) - if (res.code === 200) { + if (res.code === 1) { message.success(t('common.saveSuccess')) } } catch (error) { @@ -270,6 +343,12 @@ const handleReset = () => { message.info(t('common.resetSuccess')) } +// 处理图片上传 +const handleUpload = (field) => { + message.info('图片上传功能待实现') + // TODO: 实现图片上传逻辑 +} + onMounted(() => { fetchFields() }) @@ -317,6 +396,16 @@ onMounted(() => { color: #8c8c8c; line-height: 1.4; } + + .image-uploader { + .image-preview { + max-width: 200px; + max-height: 200px; + border: 1px solid #d9d9d9; + border-radius: 4px; + padding: 4px; + } + } } .save-actions {
{{crontabType[item.type]}}
{{item.command}}
{{item.expression}}
添加计划任务
{{logDetail}}