From d2dfb4069dcb476b6b186e83f101c2edb24cd6e3 Mon Sep 17 00:00:00 2001 From: wangrui233 Date: Mon, 26 Oct 2026 23:17:06 +0800 Subject: [PATCH] permission --- .env.development | 4 +- mock/data.ts | 89 +++-- mock/index.ts | 212 +++++++++-- mock/plugin.ts | 9 +- src/api/auth/index.ts | 20 +- src/api/dashboard/index.ts | 13 +- src/api/miniapp/index.ts | 39 ++ src/api/monitor/index.ts | 16 + src/api/system/admin/index.ts | 48 +++ src/api/system/permission/index.ts | 32 ++ src/api/system/role/index.ts | 42 ++ src/api/system/user/index.ts | 33 +- src/components/BarChart.vue | 127 +++++++ src/config/static-routes.ts | 85 +++++ src/enums/miniapp.ts | 14 + src/enums/rbac.ts | 26 ++ src/router/index.ts | 26 +- src/store/modules/user.ts | 49 ++- src/types/auth.ts | 7 +- src/types/common.ts | 6 + src/types/dashboard.ts | 20 +- src/types/miniapp/index.ts | 35 ++ src/types/monitor.ts | 70 ++++ src/types/system/rbac.ts | 114 ++++++ src/types/system/user.ts | 34 +- src/utils/request.ts | 4 +- .../components/ServerMonitorPanel.vue | 358 ++++++++++++++++++ src/views/dashboard/index.vue | 182 ++++----- src/views/login/index.vue | 4 +- src/views/miniapp/components/MiniAppForm.vue | 171 +++++++++ src/views/miniapp/index.vue | 196 ++++++++++ .../system/admin/components/AdminForm.vue | 135 +++++++ .../admin/components/AdminResetPwdDialog.vue | 73 ++++ .../admin/components/AdminRoleDialog.vue | 82 ++++ src/views/system/admin/index.vue | 187 +++++++++ .../permission/components/PermissionForm.vue | 191 ++++++++++ src/views/system/permission/index.vue | 156 ++++++++ src/views/system/role/components/RoleForm.vue | 119 ++++++ .../role/components/RolePermissionDialog.vue | 111 ++++++ src/views/system/role/index.vue | 165 ++++++++ src/views/system/user/components/UserForm.vue | 41 +- src/views/system/user/index.vue | 114 ++---- 42 files changed, 3113 insertions(+), 346 deletions(-) create mode 100644 src/api/miniapp/index.ts create mode 100644 src/api/monitor/index.ts create mode 100644 src/api/system/admin/index.ts create mode 100644 src/api/system/permission/index.ts create mode 100644 src/api/system/role/index.ts create mode 100644 src/components/BarChart.vue create mode 100644 src/config/static-routes.ts create mode 100644 src/enums/miniapp.ts create mode 100644 src/enums/rbac.ts create mode 100644 src/types/miniapp/index.ts create mode 100644 src/types/monitor.ts create mode 100644 src/types/system/rbac.ts create mode 100644 src/views/dashboard/components/ServerMonitorPanel.vue create mode 100644 src/views/miniapp/components/MiniAppForm.vue create mode 100644 src/views/miniapp/index.vue create mode 100644 src/views/system/admin/components/AdminForm.vue create mode 100644 src/views/system/admin/components/AdminResetPwdDialog.vue create mode 100644 src/views/system/admin/components/AdminRoleDialog.vue create mode 100644 src/views/system/admin/index.vue create mode 100644 src/views/system/permission/components/PermissionForm.vue create mode 100644 src/views/system/permission/index.vue create mode 100644 src/views/system/role/components/RoleForm.vue create mode 100644 src/views/system/role/components/RolePermissionDialog.vue create mode 100644 src/views/system/role/index.vue diff --git a/.env.development b/.env.development index 59b1a6d..b56c594 100644 --- a/.env.development +++ b/.env.development @@ -10,8 +10,8 @@ VITE_APP_BASE_API = '/api/v1' # 后端真实地址 VITE_APP_API_URL = 'http://localhost:8080' -# 是否启用本地 mock(true 时拦截 /api/v1 请求) -VITE_USE_MOCK = 'true' +# 是否启用本地 mock(true 时拦截全部 /api/v1 请求;false 时仅命中标记 alwaysMock 的接口) +VITE_USE_MOCK = 'false' # 开发端口 VITE_APP_PORT = '3000' diff --git a/mock/data.ts b/mock/data.ts index 73e6ccf..ca490a8 100644 --- a/mock/data.ts +++ b/mock/data.ts @@ -7,28 +7,55 @@ export type UserStatus = (typeof USER_STATUS)[keyof typeof USER_STATUS] export interface MockUser { id: number - username: string + mini_id: number + openid: string + union_id: string nickname: string - mobile: string - email: string - status: UserStatus - deptName: string - roleNames: string[] - createTime: string + avatar: string + status: 0 | 1 + created_at: string + updated_at: string } -export const users: MockUser[] = Array.from({ length: 36 }, (_, index) => { +export const users: MockUser[] = Array.from({ length: 15 }, (_, index) => { const id = index + 1 return { id, - username: `user${String(id).padStart(3, '0')}`, - nickname: `测试用户${id}`, - mobile: `13${String(100000000 + id).slice(0, 9)}`, - email: `user${id}@example.com`, - status: id % 5 === 0 ? USER_STATUS.DISABLED : USER_STATUS.ENABLED, - deptName: id % 3 === 0 ? '运营部' : '技术部', - roleNames: id % 4 === 0 ? ['管理员'] : ['普通用户'], - createTime: new Date(Date.now() - id * 86400000).toISOString(), + mini_id: (id % 3) + 1, + openid: `oABC${String(100000 + id)}`, + union_id: id % 2 === 0 ? `uXYZ${id}` : '', + nickname: `用户${id}`, + avatar: `https://picsum.photos/seed/${id}/80`, + status: id % 4 === 0 ? 0 : 1, + created_at: new Date(Date.now() - id * 86400000).toISOString(), + updated_at: new Date(Date.now() - id * 3600000).toISOString(), + } +}) + +export interface MockMiniApp { + id: number + name: string + description: string + app_id: string + app_secret: string + status: 0 | 1 + ext: string + created_at: string + updated_at: string +} + +export const miniApps: MockMiniApp[] = Array.from({ length: 12 }, (_, index) => { + const id = index + 1 + return { + id, + name: `小程序${id}`, + description: `用于演示的第 ${id} 个小程序`, + app_id: `wx${String(1000000000 + id * 137)}`, + app_secret: `secret-${id}`, + status: id % 3 === 0 ? 0 : 1, + ext: '{}', + created_at: new Date(Date.now() - id * 86400000).toISOString(), + updated_at: new Date(Date.now() - id * 3600000).toISOString(), } }) @@ -47,20 +74,18 @@ export const menuRoutes = [ }, ], }, + { + path: '/miniapp', + component: 'Layout', + redirect: '/miniapp/list', + meta: { title: '小程序管理', icon: 'Menu', hidden: false }, + children: [ + { + path: 'list', + component: 'miniapp/index', + name: 'Miniapp', + meta: { title: '小程序管理', icon: 'Menu', hidden: false }, + }, + ], + }, ] - -export const dashboardSummary = { - userTotal: 1024, - orderTotal: 8672, - gmv: 1289340.55, - pendingApproval: 12, - trend: [ - { date: '10-01', value: 120 }, - { date: '10-02', value: 200 }, - { date: '10-03', value: 150 }, - { date: '10-04', value: 320 }, - { date: '10-05', value: 280 }, - { date: '10-06', value: 410 }, - { date: '10-07', value: 365 }, - ], -} diff --git a/mock/index.ts b/mock/index.ts index 486386e..c66e4fd 100644 --- a/mock/index.ts +++ b/mock/index.ts @@ -1,4 +1,10 @@ -import { dashboardSummary, menuRoutes, USER_STATUS, users, type MockUser } from './data' +import { + menuRoutes, + miniApps, + users, + USER_STATUS, + type MockMiniApp, +} from './data' export interface MockContext { url: string @@ -11,6 +17,8 @@ export interface MockRule { method: 'GET' | 'POST' | 'PUT' | 'DELETE' pattern: RegExp handler: (ctx: MockContext) => unknown + /** 全局 mock 关闭时仍生效(用于单独 mock 新增接口) */ + alwaysMock?: boolean } const toNumber = (value: unknown, fallback = 0) => { @@ -46,7 +54,16 @@ export const mockRules: MockRule[] = [ nickname: '超级管理员', avatar: '', roles: ['ADMIN'], - permissions: ['sys:user:add', 'sys:user:edit', 'sys:user:delete', 'sys:user:query'], + permissions: [ + 'sys:user:add', + 'sys:user:edit', + 'sys:user:delete', + 'sys:user:query', + 'miniapp:add', + 'miniapp:edit', + 'miniapp:delete', + 'miniapp:query', + ], }), }, { @@ -56,74 +73,201 @@ export const mockRules: MockRule[] = [ }, { method: 'GET', - pattern: /^\/api\/v1\/users\/page$/, + pattern: /^\/api\/v1\/api\/admin\/users$/, handler: (ctx) => { - const { username, status } = ctx.query - const pageNum = toNumber(ctx.query.pageNum, 1) - const pageSize = toNumber(ctx.query.pageSize, 10) + const page = toNumber(ctx.query.page, 1) + const size = toNumber(ctx.query.size, 20) + const miniId = ctx.query.mini_id ? toNumber(ctx.query.mini_id) : 0 + const keyword = (ctx.query.keyword as string) || '' const filtered = users.filter((item) => { - const matchName = !username || item.username.includes(username) - const matchStatus = !status || String(item.status) === status - return matchName && matchStatus + if (miniId && item.mini_id !== miniId) return false + if (keyword && !item.nickname.includes(keyword) && !item.openid.includes(keyword)) return false + return true }) - const start = (pageNum - 1) * pageSize - return { list: filtered.slice(start, start + pageSize), total: filtered.length } + const start = (page - 1) * size + return { list: filtered.slice(start, start + size), total: filtered.length } }, }, { method: 'GET', - pattern: /^\/api\/v1\/users\/(\d+)$/, + pattern: /^\/api\/v1\/api\/admin\/users\/(\d+)$/, handler: (ctx) => users.find((item) => item.id === toNumber(ctx.params[0])) ?? null, }, { - method: 'POST', - pattern: /^\/api\/v1\/users$/, + method: 'PUT', + pattern: /^\/api\/v1\/api\/admin\/users\/(\d+)$/, handler: (ctx) => { - const id = users.length + 1 - const created: MockUser = { + const target = users.find((item) => item.id === toNumber(ctx.params[0])) + if (!target) return false + const body = ctx.body as Record + if (typeof body.nickname === 'string') target.nickname = body.nickname + if (typeof body.avatar === 'string') target.avatar = body.avatar + if (typeof body.status !== 'undefined') target.status = toNumber(body.status) === 0 ? 0 : 1 + target.updated_at = new Date().toISOString() + return target + }, + }, + { + method: 'POST', + pattern: /^\/api\/v1\/api\/admin\/users\/(\d+)\/status$/, + handler: (ctx) => { + const target = users.find((item) => item.id === toNumber(ctx.params[0])) + if (!target) return false + const body = ctx.body as { status?: number } + target.status = toNumber(body.status) === 0 ? 0 : 1 + target.updated_at = new Date().toISOString() + return target + }, + }, + { + method: 'GET', + pattern: /^\/api\/v1\/api\/admin\/miniapps$/, + handler: (ctx) => { + const { status } = ctx.query + const pageNum = toNumber(ctx.query.pageNum, 1) + const pageSize = toNumber(ctx.query.pageSize, 10) + + const filtered = miniApps.filter((item) => !status || String(item.status) === status) + const start = (pageNum - 1) * pageSize + const list = filtered.slice(start, start + pageSize).map((item) => { + const { app_secret: _app_secret, ...rest } = item + return rest + }) + return { list, total: filtered.length } + }, + }, + { + method: 'GET', + pattern: /^\/api\/v1\/api\/admin\/miniapps\/(\d+)$/, + handler: (ctx) => { + const target = miniApps.find((item) => item.id === toNumber(ctx.params[0])) + if (!target) return null + const { app_secret: _app_secret, ...rest } = target + return rest + }, + }, + { + method: 'POST', + pattern: /^\/api\/v1\/api\/admin\/miniapps$/, + handler: (ctx) => { + const id = miniApps.length + 1 + const created: MockMiniApp = { id, - username: pickString(ctx.body.username, `user${id}`), - nickname: pickString(ctx.body.nickname), - mobile: pickString(ctx.body.mobile), - email: pickString(ctx.body.email), + name: pickString(ctx.body.name), + description: pickString(ctx.body.description), + app_id: pickString(ctx.body.app_id), + app_secret: pickString(ctx.body.app_secret), status: toNumber(ctx.body.status, USER_STATUS.ENABLED) === 0 ? 0 : 1, - deptName: pickString(ctx.body.deptName, '技术部'), - roleNames: Array.isArray(ctx.body.roleNames) - ? (ctx.body.roleNames as string[]) - : ['普通用户'], - createTime: new Date().toISOString(), + ext: pickString(ctx.body.ext, '{}'), + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), } - users.unshift(created) + miniApps.unshift(created) return id }, }, { method: 'PUT', - pattern: /^\/api\/v1\/users\/(\d+)$/, + pattern: /^\/api\/v1\/api\/admin\/miniapps\/(\d+)$/, handler: (ctx) => { - const target = users.find((item) => item.id === toNumber(ctx.params[0])) + const target = miniApps.find((item) => item.id === toNumber(ctx.params[0])) if (!target) return false - Object.assign(target, ctx.body) + const body = ctx.body as Record + if (typeof body.name === 'string') target.name = body.name + if (typeof body.description === 'string') target.description = body.description + if (typeof body.app_id === 'string') target.app_id = body.app_id + if (typeof body.app_secret === 'string' && body.app_secret) target.app_secret = body.app_secret + if (typeof body.status !== 'undefined') target.status = toNumber(body.status) === 0 ? 0 : 1 + if (typeof body.ext === 'string') target.ext = body.ext + target.updated_at = new Date().toISOString() return true }, }, { method: 'DELETE', - pattern: /^\/api\/v1\/users\/([\d,]+)$/, + pattern: /^\/api\/v1\/api\/admin\/miniapps\/([\d,]+)$/, handler: (ctx) => { const ids = ctx.params[0].split(',').map((id) => toNumber(id)) ids.forEach((id) => { - const index = users.findIndex((item) => item.id === id) - if (index > -1) users.splice(index, 1) + const index = miniApps.findIndex((item) => item.id === id) + if (index > -1) miniApps.splice(index, 1) }) return true }, }, { + // 统计每个小程序的用户数(有真实用户的取真实值,其余返回稳定模拟值) method: 'GET', - pattern: /^\/api\/v1\/dashboard\/summary$/, - handler: () => dashboardSummary, + pattern: /^\/api\/v1\/api\/admin\/dashboard\/user-count-by-miniapp$/, + alwaysMock: true, + handler: () => + miniApps.map((app) => { + const real = users.filter((item) => item.mini_id === app.id).length + return { + miniAppId: app.id, + miniAppName: app.name, + userCount: real > 0 ? real : ((app.id * 37) % 200) + 8, + } + }), + }, + { + method: 'GET', + pattern: /^\/api\/v1\/api\/admin\/monitor\/server-info$/, + alwaysMock: true, + handler: () => ({ + hostname: 'portal-server-01', + os: 'Linux', + osVersion: 'Ubuntu 22.04.3 LTS', + arch: 'amd64', + kernel: '5.15.0-91-generic', + cpuModel: 'Intel(R) Xeon(R) Platinum 8369B CPU @ 2.70GHz', + cpuCores: 8, + runtimeVersion: 'go1.21.5', + uptime: 1234560, + }), + }, + { + // 实时状态:每次请求生成浮动指标,模拟监控采集中 + method: 'GET', + pattern: /^\/api\/v1\/api\/admin\/monitor\/server-status$/, + alwaysMock: true, + handler: () => { + const cpuUsage = Math.round((20 + Math.random() * 60) * 10) / 10 + const memTotal = 16 * 1024 ** 3 + const memUsed = Math.round(memTotal * (0.4 + Math.random() * 0.3)) + const loadBase = (cpuUsage / 100) * 8 + const gb = 1024 ** 3 + const disks = [ + { mount: '/', filesystem: '/dev/vda1', total: 100 * gb, used: 62 * gb, free: 38 * gb }, + { mount: '/data', filesystem: '/dev/vdb1', total: 500 * gb, used: 311 * gb, free: 189 * gb }, + { mount: '/logs', filesystem: '/dev/vdc1', total: 50 * gb, used: 9 * gb, free: 41 * gb }, + ].map((disk) => ({ + ...disk, + usagePercent: Math.round((disk.used / disk.total) * 1000) / 10, + })) + + return { + cpu: { + usagePercent: cpuUsage, + cores: 8, + model: 'Intel(R) Xeon(R) Platinum 8369B CPU @ 2.70GHz', + loadAverage: [ + Math.round((loadBase + Math.random()) * 100) / 100, + Math.round((loadBase + Math.random()) * 100) / 100, + Math.round((loadBase + Math.random()) * 100) / 100, + ], + }, + memory: { + total: memTotal, + used: memUsed, + free: memTotal - memUsed, + usagePercent: Math.round((memUsed / memTotal) * 1000) / 10, + }, + disks, + timestamp: Date.now(), + } + }, }, ] diff --git a/mock/plugin.ts b/mock/plugin.ts index a934669..e6ecd8a 100644 --- a/mock/plugin.ts +++ b/mock/plugin.ts @@ -12,15 +12,15 @@ function readBody(req: NodeJS.ReadableStream): Promise { } /** - * 本地 mock 中间件:仅在 dev 且 VITE_USE_MOCK=true 时拦截 /api/v1 请求。 + * 本地 mock 中间件:始终挂载于 dev,但仅命中「全局 mock 开启」或标记 alwaysMock 的规则。 + * 这样新增页面的接口可被单独 mock,而项目其余接口仍走真实后端代理。 * 不引入三方 mock 依赖,避免污染生产依赖树。 */ -export function mockPlugin(enabled = true): Plugin { +export function mockPlugin(globalMock = false): Plugin { return { name: 'portal-mock', apply: 'serve', configureServer(server) { - if (!enabled) return server.middlewares.use((req, res, next) => { if (!req.url?.startsWith(MOCK_PREFIX)) return next() @@ -30,7 +30,8 @@ export function mockPlugin(enabled = true): Plugin { const rule = mockRules.find( (item) => item.method === (req.method ?? 'GET').toUpperCase() && - item.pattern.test(requestUrl.pathname), + item.pattern.test(requestUrl.pathname) && + (globalMock || item.alwaysMock === true), ) if (!rule) return next() diff --git a/src/api/auth/index.ts b/src/api/auth/index.ts index 99a9109..986065a 100644 --- a/src/api/auth/index.ts +++ b/src/api/auth/index.ts @@ -1,27 +1,29 @@ import request from '@/utils/request' import type { LoginForm, LoginResult, UserInfo } from '@/types/auth' import type { RouteVO } from '@/types/router' +import { staticMenuRoutes } from '@/config/static-routes' -const BASE_URL = '/auth' +// 叠加在 VITE_APP_BASE_API(/api/v1) 之上,经 Vite proxy 剥离 /api/v1 后命中后端 /api/admin +const ADMIN_BASE = '/api/admin' export const AuthAPI = { - /** 登录 */ + /** 登录,返回 JWT(对应后端 POST /api/admin/login) */ login(data: LoginForm) { - return request({ url: `${BASE_URL}/login`, method: 'post', data }) + return request({ url: `${ADMIN_BASE}/login`, method: 'post', data }) }, - /** 登出 */ + /** 后端未提供登出接口,仅前端清理登录态 */ logout() { - return request({ url: `${BASE_URL}/logout`, method: 'post' }) + return Promise.resolve(true) }, - /** 获取当前登录用户信息(含角色与权限) */ + /** 当前登录管理员信息(对应后端 GET /api/admin/info) */ getMe() { - return request({ url: `${BASE_URL}/me`, method: 'get' }) + return request({ url: `${ADMIN_BASE}/info`, method: 'get' }) }, - /** 获取当前用户可访问的菜单路由 */ + /** 后端未提供菜单接口,返回前端静态路由(含小程序管理) */ getRoutes() { - return request({ url: `${BASE_URL}/routes`, method: 'get' }) + return Promise.resolve(staticMenuRoutes as RouteVO[]) }, } diff --git a/src/api/dashboard/index.ts b/src/api/dashboard/index.ts index 204099a..ebce7a7 100644 --- a/src/api/dashboard/index.ts +++ b/src/api/dashboard/index.ts @@ -1,11 +1,14 @@ import request from '@/utils/request' -import type { DashboardSummary } from '@/types/dashboard' +import type { MiniAppUserCountVO } from '@/types/dashboard' -const BASE_URL = '/dashboard' +const BASE_URL = '/api/admin/dashboard' export const DashboardAPI = { - /** 首页汇总数据 */ - summary() { - return request({ url: `${BASE_URL}/summary`, method: 'get' }) + /** 统计每个小程序的用户数量 */ + statUserCountByMiniApp() { + return request({ + url: `${BASE_URL}/user-count-by-miniapp`, + method: 'get', + }) }, } diff --git a/src/api/miniapp/index.ts b/src/api/miniapp/index.ts new file mode 100644 index 0000000..10856c1 --- /dev/null +++ b/src/api/miniapp/index.ts @@ -0,0 +1,39 @@ +import request from '@/utils/request' +import type { PageResult } from '@/types/common' +import type { MiniAppForm, MiniAppQuery, MiniAppStatus, MiniAppVO } from '@/types/miniapp' + +// 前端基础路径 VITE_APP_BASE_API 为 /api/v1,经 Vite proxy 整体剥离后转发到 Go 后端; +// 此处拼接 /api/admin/miniapps,剥离 /api/v1 后即可精确命中后端 /api/admin/miniapps +const BASE_URL = '/api/admin/miniapps' + +export const MiniAppAPI = { + /** 分页查询小程序列表 */ + query(params: MiniAppQuery) { + return request>({ url: BASE_URL, method: 'get', params }) + }, + + /** 小程序详情 */ + detail(id: number) { + return request({ url: `${BASE_URL}/${id}`, method: 'get' }) + }, + + /** 新增小程序 */ + create(data: MiniAppForm) { + return request({ url: BASE_URL, method: 'post', data }) + }, + + /** 修改小程序 */ + update(id: number, data: MiniAppForm) { + return request({ url: `${BASE_URL}/${id}`, method: 'put', data }) + }, + + /** 删除小程序,ids 为逗号分隔 */ + delete(ids: string) { + return request({ url: `${BASE_URL}/${ids}`, method: 'delete' }) + }, + + /** 启用 / 停用小程序 */ + updateStatus(id: number, status: MiniAppStatus) { + return request({ url: `${BASE_URL}/${id}/status`, method: 'put', data: { status } }) + }, +} diff --git a/src/api/monitor/index.ts b/src/api/monitor/index.ts new file mode 100644 index 0000000..ab00090 --- /dev/null +++ b/src/api/monitor/index.ts @@ -0,0 +1,16 @@ +import request from '@/utils/request' +import type { ServerInfoVO, ServerStatusVO } from '@/types/monitor' + +const BASE_URL = '/api/admin/monitor' + +export const MonitorAPI = { + /** 获取服务器基础信息 */ + getServerInfo() { + return request({ url: `${BASE_URL}/server-info`, method: 'get' }) + }, + + /** 获取服务器实时状态(CPU / 内存 / 磁盘 / 负载) */ + getServerStatus() { + return request({ url: `${BASE_URL}/server-status`, method: 'get' }) + }, +} diff --git a/src/api/system/admin/index.ts b/src/api/system/admin/index.ts new file mode 100644 index 0000000..ce2be2c --- /dev/null +++ b/src/api/system/admin/index.ts @@ -0,0 +1,48 @@ +import request from '@/utils/request' +import type { PageResult } from '@/types/common' +import type { + AdminForm, + AdminQuery, + AdminResetPwdForm, + AdminRoleForm, + AdminVO, +} from '@/types/system/rbac' + +const BASE_URL = '/api/admin/rbac/admins' + +export const AdminAPI = { + /** 分页查询管理员列表 */ + query(params: AdminQuery) { + return request>({ url: BASE_URL, method: 'get', params }) + }, + + /** 管理员详情 */ + detail(id: number) { + return request({ url: `${BASE_URL}/${id}`, method: 'get' }) + }, + + /** 新增管理员 */ + create(data: AdminForm) { + return request({ url: BASE_URL, method: 'post', data }) + }, + + /** 编辑管理员昵称 / 状态 */ + update(id: number, data: AdminForm) { + return request({ url: `${BASE_URL}/${id}`, method: 'put', data }) + }, + + /** 删除管理员 */ + delete(id: number) { + return request({ url: `${BASE_URL}/${id}`, method: 'delete' }) + }, + + /** 分配角色(全量替换) */ + assignRoles(id: number, data: AdminRoleForm) { + return request({ url: `${BASE_URL}/${id}/roles`, method: 'post', data }) + }, + + /** 重置密码 */ + resetPassword(id: number, data: AdminResetPwdForm) { + return request({ url: `${BASE_URL}/${id}/reset-pwd`, method: 'post', data }) + }, +} diff --git a/src/api/system/permission/index.ts b/src/api/system/permission/index.ts new file mode 100644 index 0000000..e1da128 --- /dev/null +++ b/src/api/system/permission/index.ts @@ -0,0 +1,32 @@ +import request from '@/utils/request' +import type { PageResult } from '@/types/common' +import type { PermissionForm, PermissionQuery, PermissionVO } from '@/types/system/rbac' + +const BASE_URL = '/api/admin/rbac/permissions' + +export const PermissionAPI = { + /** 分页查询权限列表 */ + query(params: PermissionQuery) { + return request>({ url: BASE_URL, method: 'get', params }) + }, + + /** 权限详情 */ + detail(id: number) { + return request({ url: `${BASE_URL}/${id}`, method: 'get' }) + }, + + /** 新增权限 */ + create(data: PermissionForm) { + return request({ url: BASE_URL, method: 'post', data }) + }, + + /** 编辑权限 */ + update(id: number, data: PermissionForm) { + return request({ url: `${BASE_URL}/${id}`, method: 'put', data }) + }, + + /** 删除权限 */ + delete(id: number) { + return request({ url: `${BASE_URL}/${id}`, method: 'delete' }) + }, +} diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts new file mode 100644 index 0000000..93e7c79 --- /dev/null +++ b/src/api/system/role/index.ts @@ -0,0 +1,42 @@ +import request from '@/utils/request' +import type { PageResult } from '@/types/common' +import type { + RoleForm, + RolePermissionForm, + RoleQuery, + RoleVO, +} from '@/types/system/rbac' + +const BASE_URL = '/api/admin/rbac/roles' + +export const RoleAPI = { + /** 分页查询角色列表 */ + query(params: RoleQuery) { + return request>({ url: BASE_URL, method: 'get', params }) + }, + + /** 角色详情 */ + detail(id: number) { + return request({ url: `${BASE_URL}/${id}`, method: 'get' }) + }, + + /** 新增角色 */ + create(data: RoleForm) { + return request({ url: BASE_URL, method: 'post', data }) + }, + + /** 编辑角色 */ + update(id: number, data: RoleForm) { + return request({ url: `${BASE_URL}/${id}`, method: 'put', data }) + }, + + /** 删除角色 */ + delete(id: number) { + return request({ url: `${BASE_URL}/${id}`, method: 'delete' }) + }, + + /** 分配权限(全量替换) */ + assignPermissions(id: number, data: RolePermissionForm) { + return request({ url: `${BASE_URL}/${id}/permissions`, method: 'post', data }) + }, +} diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts index 846d5ba..d4a1621 100644 --- a/src/api/system/user/index.ts +++ b/src/api/system/user/index.ts @@ -1,13 +1,23 @@ import request from '@/utils/request' import type { PageResult } from '@/types/common' -import type { UserForm, UserQuery, UserVO } from '@/types/system/user' +import type { UserForm, UserQuery, UserStatus, UserVO } from '@/types/system/user' -const BASE_URL = '/users' +// 叠加在 VITE_APP_BASE_API(/api/v1) 上,经 Vite proxy 剥离 /api/v1 后命中后端 /api/admin/users +const BASE_URL = '/api/admin/users' export const UserAPI = { - /** 分页查询用户列表 */ + /** 分页查询小程序端用户列表(pageNum/pageSize 映射为后端 page/size) */ query(params: UserQuery) { - return request>({ url: `${BASE_URL}/page`, method: 'get', params }) + return request>({ + url: BASE_URL, + method: 'get', + params: { + mini_id: params.mini_id ? Number(params.mini_id) : undefined, + keyword: params.keyword, + page: params.pageNum, + size: params.pageSize, + }, + }) }, /** 用户详情 */ @@ -15,18 +25,13 @@ export const UserAPI = { return request({ url: `${BASE_URL}/${id}`, method: 'get' }) }, - /** 新增用户 */ - create(data: UserForm) { - return request({ url: BASE_URL, method: 'post', data }) - }, - - /** 修改用户 */ + /** 编辑用户(昵称 / 头像 / 状态,按需提供) */ update(id: number, data: UserForm) { - return request({ url: `${BASE_URL}/${id}`, method: 'put', data }) + return request({ url: `${BASE_URL}/${id}`, method: 'put', data }) }, - /** 删除用户,ids 为逗号分隔 */ - delete(ids: string) { - return request({ url: `${BASE_URL}/${ids}`, method: 'delete' }) + /** 禁用 / 启用用户 */ + updateStatus(id: number, status: UserStatus) { + return request({ url: `${BASE_URL}/${id}/status`, method: 'post', data: { status } }) }, } diff --git a/src/components/BarChart.vue b/src/components/BarChart.vue new file mode 100644 index 0000000..92f7b7e --- /dev/null +++ b/src/components/BarChart.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/config/static-routes.ts b/src/config/static-routes.ts new file mode 100644 index 0000000..c9e7d0f --- /dev/null +++ b/src/config/static-routes.ts @@ -0,0 +1,85 @@ +import type { RouteVO } from '@/types/router' + +/** + * 后端当前未提供菜单/路由接口,这里作为前端静态路由兜底。 + * 含「小程序管理」菜单,保证关闭 mock 后页面仍可访问。 + * 待后端提供 routes 接口后,应改回 AuthAPI.getRoutes() 动态返回。 + */ +export const staticMenuRoutes: RouteVO[] = [ + { + path: '/dashboard', + component: 'Layout', + redirect: '/dashboard/index', + meta: { title: '概览', icon: 'DataBoard', hidden: false }, + children: [ + { + path: 'index', + component: 'dashboard/index', + name: 'Dashboard', + meta: { title: '概览', icon: 'DataBoard', hidden: false }, + }, + ], + }, + { + path: '/system', + component: 'Layout', + redirect: '/system/user', + meta: { title: '系统管理', icon: 'Setting', hidden: false }, + children: [ + { + path: 'user', + component: 'system/user/index', + name: 'User', + meta: { title: '用户管理', icon: 'User', hidden: false }, + }, + ], + }, + { + path: '/personnel', + component: 'Layout', + redirect: '/personnel/admin', + meta: { title: '人员管理', icon: 'UserFilled', hidden: false }, + children: [ + { + path: 'admin', + component: 'system/admin/index', + name: 'AdminList', + meta: { title: '管理员管理', icon: 'User', hidden: false }, + }, + ], + }, + { + path: '/rbac', + component: 'Layout', + redirect: '/rbac/role', + meta: { title: '权限管理', icon: 'Lock', hidden: false }, + children: [ + { + path: 'role', + component: 'system/role/index', + name: 'RoleList', + meta: { title: '角色管理', icon: 'Avatar', hidden: false }, + }, + { + path: 'permission', + component: 'system/permission/index', + name: 'PermissionList', + meta: { title: '权限管理', icon: 'Key', hidden: false }, + }, + ], + }, + { + path: '/miniapp', + component: 'Layout', + redirect: '/miniapp/list', + meta: { title: '小程序管理', icon: 'Menu', hidden: false }, + children: [ + { + path: 'list', + component: 'miniapp/index', + name: 'Miniapp', + meta: { title: '小程序管理', icon: 'Menu', hidden: false }, + }, + ], + }, +] diff --git a/src/enums/miniapp.ts b/src/enums/miniapp.ts new file mode 100644 index 0000000..d1ce76e --- /dev/null +++ b/src/enums/miniapp.ts @@ -0,0 +1,14 @@ +import type { Option } from '@/types/common' +import type { MiniAppStatus } from '@/types/miniapp' + +/** 小程序状态枚举 */ +export const MiniAppStatusEnum = { + ENABLED: 1, + DISABLED: 0, +} as const + +/** 小程序状态下拉选项 */ +export const MINI_APP_STATUS_OPTIONS: Option[] = [ + { label: '启用', value: MiniAppStatusEnum.ENABLED }, + { label: '停用', value: MiniAppStatusEnum.DISABLED }, +] diff --git a/src/enums/rbac.ts b/src/enums/rbac.ts new file mode 100644 index 0000000..043d4f8 --- /dev/null +++ b/src/enums/rbac.ts @@ -0,0 +1,26 @@ +import type { Option } from '@/types/common' +import type { PermissionType, RbacStatus } from '@/types/system/rbac' + +/** 启用 / 禁用状态(管理员与角色共用) */ +export const RbacStatusEnum = { + DISABLED: 0, + ENABLED: 1, +} as const + +/** 状态下拉选项 */ +export const RBAC_STATUS_OPTIONS: Option[] = [ + { label: '启用', value: RbacStatusEnum.ENABLED }, + { label: '禁用', value: RbacStatusEnum.DISABLED }, +] + +/** 权限类型 */ +export const PermissionTypeEnum = { + API: 'api', + MENU: 'menu', +} as const + +/** 权限类型下拉选项 */ +export const PERMISSION_TYPE_OPTIONS: Option[] = [ + { label: '接口', value: PermissionTypeEnum.API }, + { label: '菜单', value: PermissionTypeEnum.MENU }, +] diff --git a/src/router/index.ts b/src/router/index.ts index d8ca828..4322c04 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -4,6 +4,19 @@ import { usePermissionStore, useUserStore } from '@/store' const DEFAULT_TITLE = import.meta.env.VITE_APP_TITLE || 'Portal 管理后台' +/** 取动态路由中的第一个叶子页面作为首页重定向目标 */ +function findFirstRoutePath(routes: RouteRecordRaw[]): string | undefined { + for (const route of routes) { + if (route.children?.length) { + const childPath = findFirstRoutePath(route.children) + if (childPath) return childPath + } else if (route.path && route.path !== '/') { + return route.path + } + } + return undefined +} + /** 静态路由:登录页与首页骨架,不依赖后端返回 */ export const constantRoutes: RouteRecordRaw[] = [ { @@ -15,15 +28,10 @@ export const constantRoutes: RouteRecordRaw[] = [ { path: '/', component: () => import('@/layouts/index.vue'), - redirect: '/dashboard', - children: [ - { - path: 'dashboard', - name: 'Dashboard', - component: () => import('@/views/dashboard/index.vue'), - meta: { title: '仪表盘', icon: 'Odometer' }, - }, - ], + redirect: () => { + const permissionStore = usePermissionStore() + return findFirstRoutePath(permissionStore.routes) ?? '/login' + }, }, ] diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index bd0cbd9..42b0e8f 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -3,6 +3,21 @@ import { AuthAPI } from '@/api/auth' import type { LoginForm, UserInfo } from '@/types/auth' import { getToken, removeToken, setToken } from '@/utils/storage' +/** 后端未提供角色/权限接口时的兜底权限,保证按钮级权限(含小程序管理)可用 */ +const DEFAULT_PERMISSIONS = [ + 'sys:user:add', + 'sys:user:edit', + 'sys:user:delete', + 'sys:user:query', + 'admin:manage', + 'role:manage', + 'perm:manage', + 'miniapp:add', + 'miniapp:edit', + 'miniapp:delete', + 'miniapp:query', +] + export const useUserStore = defineStore('user', () => { /** Token 初始从本地恢复,避免刷新后丢失登录态 */ const token = ref(getToken()) @@ -15,17 +30,37 @@ export const useUserStore = defineStore('user', () => { /** 登录 */ async function login(data: LoginForm) { const result = await AuthAPI.login(data) - token.value = result.accessToken - setToken(result.accessToken) + token.value = result.token + setToken(result.token) } /** 拉取用户信息(角色 + 权限码) */ async function getUserInfo() { - const data = await AuthAPI.getMe() - userInfo.value = data - roles.value = data.roles ?? [] - permissions.value = data.permissions ?? [] - return data + try { + const data = await AuthAPI.getMe() + const raw = data as UserInfo & { admin_id?: string } + userInfo.value = { + userId: Number(raw.admin_id ?? raw.userId ?? 1), + username: raw.username || 'admin', + nickname: raw.nickname || '管理员', + avatar: raw.avatar || '', + roles: raw.roles?.length ? raw.roles : ['ADMIN'], + permissions: raw.permissions?.length ? raw.permissions : DEFAULT_PERMISSIONS, + } + } catch { + // 后端未返回用户信息时兜底,确保页面与按钮权限可用 + userInfo.value = { + userId: 1, + username: 'admin', + nickname: '管理员', + avatar: '', + roles: ['ADMIN'], + permissions: DEFAULT_PERMISSIONS, + } + } + roles.value = userInfo.value.roles + permissions.value = userInfo.value.permissions + return userInfo.value } /** 登出 */ diff --git a/src/types/auth.ts b/src/types/auth.ts index fb8082e..c2c4582 100644 --- a/src/types/auth.ts +++ b/src/types/auth.ts @@ -8,12 +8,9 @@ export interface LoginForm { captchaCode?: string } -/** 登录响应 */ +/** 登录响应(对应后端 data.token) */ export interface LoginResult { - accessToken: string - refreshToken?: string - tokenType: string - expiresIn?: number + token: string } /** 当前登录用户 */ diff --git a/src/types/common.ts b/src/types/common.ts index 88b9390..011900e 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -12,6 +12,12 @@ export interface PageQuery { pageSize: number } +/** RBAC 分页参数(后端使用 page / size) */ +export interface RbacPageQuery { + page: number + size: number +} + /** 分页响应结构 */ export interface PageResult { list: T[] diff --git a/src/types/dashboard.ts b/src/types/dashboard.ts index 5662588..11893be 100644 --- a/src/types/dashboard.ts +++ b/src/types/dashboard.ts @@ -1,13 +1,9 @@ -/** 仪表盘汇总数据 */ -export interface DashboardSummary { - userTotal: number - orderTotal: number - gmv: number - pendingApproval: number - trend: TrendPoint[] -} - -export interface TrendPoint { - date: string - value: number +/** 每个小程序对应的用户数统计项 */ +export interface MiniAppUserCountVO { + /** 小程序 ID */ + miniAppId: number + /** 小程序名称 */ + miniAppName: string + /** 用户数量 */ + userCount: number } diff --git a/src/types/miniapp/index.ts b/src/types/miniapp/index.ts new file mode 100644 index 0000000..7691ab9 --- /dev/null +++ b/src/types/miniapp/index.ts @@ -0,0 +1,35 @@ +import type { PageQuery } from '@/types/common' + +/** 小程序状态:1 启用 / 0 停用 */ +export type MiniAppStatus = 0 | 1 + +/** 小程序列表查询参数 */ +export interface MiniAppQuery extends PageQuery { + /** 按状态筛选,不传表示全部 */ + status?: MiniAppStatus +} + +/** 被纳管小程序实体(后端返回,app_secret 不返回) */ +export interface MiniAppVO { + id: number + name: string + description: string + app_id: string + status: MiniAppStatus + ext: string + created_at: string + updated_at: string +} + +/** 新增/编辑小程序表单 */ +export interface MiniAppForm { + id?: number + name: string + description?: string + app_id: string + /** 创建时必填;编辑时留空表示不修改 */ + app_secret?: string + status: MiniAppStatus + /** 扩展字段,需为合法 JSON 字符串 */ + ext?: string +} diff --git a/src/types/monitor.ts b/src/types/monitor.ts new file mode 100644 index 0000000..9956d46 --- /dev/null +++ b/src/types/monitor.ts @@ -0,0 +1,70 @@ +/** 服务器基础信息 */ +export interface ServerInfoVO { + /** 主机名 */ + hostname: string + /** 操作系统 */ + os: string + /** 操作系统版本 */ + osVersion: string + /** 系统架构 */ + arch: string + /** 内核版本 */ + kernel: string + /** CPU 型号 */ + cpuModel: string + /** CPU 核心数 */ + cpuCores: number + /** 运行时(Go / Node 等)版本 */ + runtimeVersion: string + /** 已运行时长,单位秒 */ + uptime: number +} + +/** CPU 实时状态 */ +export interface CpuVO { + /** 使用率百分比 0-100 */ + usagePercent: number + /** 核心数 */ + cores: number + /** CPU 型号 */ + model: string + /** 1 / 5 / 15 分钟平均负载 */ + loadAverage: [number, number, number] +} + +/** 内存状态(字节) */ +export interface MemoryVO { + /** 总内存(字节) */ + total: number + /** 已用内存(字节) */ + used: number + /** 空闲内存(字节) */ + free: number + /** 使用率百分比 0-100 */ + usagePercent: number +} + +/** 磁盘分区状态(字节) */ +export interface DiskVO { + /** 挂载点 */ + mount: string + /** 文件系统 */ + filesystem: string + /** 总容量(字节) */ + total: number + /** 已用容量(字节) */ + used: number + /** 空闲容量(字节) */ + free: number + /** 使用率百分比 0-100 */ + usagePercent: number +} + +/** 服务器实时状态聚合 */ +export interface ServerStatusVO { + cpu: CpuVO + memory: MemoryVO + disks: DiskVO[] + /** 采集时间戳(毫秒) */ + timestamp: number +} diff --git a/src/types/system/rbac.ts b/src/types/system/rbac.ts new file mode 100644 index 0000000..4c2787b --- /dev/null +++ b/src/types/system/rbac.ts @@ -0,0 +1,114 @@ +import type { RbacPageQuery } from '@/types/common' + +/** 启用(1) / 禁用(0) */ +export type RbacStatus = 0 | 1 + +/** 权限类型:api 接口权限 / menu 菜单权限 */ +export type PermissionType = 'api' | 'menu' + +/* ----------------------------- 管理员 ----------------------------- */ + +/** 管理员列表查询参数 */ +export interface AdminQuery extends RbacPageQuery { + keyword?: string + /** -1 全部,0 禁用,1 启用 */ + status?: RbacStatus | -1 +} + +/** 管理员实体(后端返回,password_hash 不返回) */ +export interface AdminVO { + id: number + username: string + nickname: string + status: RbacStatus + created_at: string + updated_at: string +} + +/** 新增/编辑管理员表单 */ +export interface AdminForm { + id?: number + /** 新增时必填,编辑时不允许修改 */ + username?: string + nickname?: string + /** 仅新增时必填 */ + password?: string + status: RbacStatus +} + +/** 分配角色(全量替换) */ +export interface AdminRoleForm { + role_ids: number[] +} + +/** 重置密码 */ +export interface AdminResetPwdForm { + password: string +} + +/* ------------------------------ 角色 ------------------------------ */ + +/** 角色列表查询参数 */ +export interface RoleQuery extends RbacPageQuery { + keyword?: string + status?: RbacStatus | -1 +} + +/** 角色实体 */ +export interface RoleVO { + id: number + code: string + name: string + description: string + status: RbacStatus + created_at: string + updated_at: string +} + +/** 新增/编辑角色表单 */ +export interface RoleForm { + id?: number + code?: string + name?: string + description?: string + status?: RbacStatus +} + +/** 分配权限(全量替换) */ +export interface RolePermissionForm { + permission_ids: number[] +} + +/* ----------------------------- 权限 ----------------------------- */ + +/** 权限列表查询参数 */ +export interface PermissionQuery extends RbacPageQuery { + /** 为空表示全部类型 */ + type?: PermissionType | '' + keyword?: string +} + +/** 权限实体 */ +export interface PermissionVO { + id: number + code: string + name: string + type: PermissionType + method: string + path: string + parent_id: number + sort: number + created_at: string +} + +/** 新增/编辑权限表单 */ +export interface PermissionForm { + id?: number + code: string + name: string + type: PermissionType + method?: string + path?: string + parent_id?: number + sort?: number +} diff --git a/src/types/system/user.ts b/src/types/system/user.ts index 89d5a43..79859af 100644 --- a/src/types/system/user.ts +++ b/src/types/system/user.ts @@ -1,35 +1,33 @@ import type { PageQuery } from '@/types/common' -/** 用户状态:1 启用 / 0 禁用 */ +/** 用户状态:1 正常 / 0 禁用 */ export type UserStatus = 0 | 1 -/** 用户列表查询参数 */ +/** 小程序端用户列表查询参数 */ export interface UserQuery extends PageQuery { - username?: string - status?: UserStatus - deptId?: number + /** 按所属小程序筛选,0 或不传表示全部 */ + mini_id?: number + /** 模糊匹配昵称 / OpenID */ + keyword?: string } -/** 用户列表实体(后端返回) */ +/** 小程序端用户实体(后端返回) */ export interface UserVO { id: number - username: string + mini_id: number + openid: string + union_id: string nickname: string - mobile: string - email: string + avatar: string status: UserStatus - deptName: string - roleNames: string[] - createTime: string + created_at: string + updated_at: string } -/** 新增/编辑表单 */ +/** 编辑用户表单(仅昵称 / 头像 / 状态可改) */ export interface UserForm { id?: number - username: string - nickname: string - mobile: string - email: string + nickname?: string + avatar?: string status: UserStatus - roleNames: string[] } diff --git a/src/utils/request.ts b/src/utils/request.ts index 6533100..1ee8bc6 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -88,7 +88,9 @@ export default function request(config: AxiosRequestConfig): Promise { } const { code, data, msg } = response.data - if (code === SUCCESS_CODE) { + // 兼容本项目 mock(code: '00000')与 Go 后端(code: 0) + const normalizedCode = String(code) + if (normalizedCode === SUCCESS_CODE || normalizedCode === '0') { return data } diff --git a/src/views/dashboard/components/ServerMonitorPanel.vue b/src/views/dashboard/components/ServerMonitorPanel.vue new file mode 100644 index 0000000..29573d5 --- /dev/null +++ b/src/views/dashboard/components/ServerMonitorPanel.vue @@ -0,0 +1,358 @@ + + + + + diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue index de58aec..c74aa4e 100644 --- a/src/views/dashboard/index.vue +++ b/src/views/dashboard/index.vue @@ -1,129 +1,137 @@ diff --git a/src/views/login/index.vue b/src/views/login/index.vue index cbf4314..ad2d745 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -16,7 +16,7 @@ const loading = ref(false) const appTitle = import.meta.env.VITE_APP_TITLE - const formData = reactive({ username: 'admin', password: '123456' }) + const formData = reactive({ username: 'admin', password: 'admin123' }) const rules: FormRules = { username: [{ required: true, message: '请输入用户名', trigger: 'blur' }], @@ -70,7 +70,7 @@ 登 录 - + diff --git a/src/views/miniapp/components/MiniAppForm.vue b/src/views/miniapp/components/MiniAppForm.vue new file mode 100644 index 0000000..0b0eef9 --- /dev/null +++ b/src/views/miniapp/components/MiniAppForm.vue @@ -0,0 +1,171 @@ + + + diff --git a/src/views/miniapp/index.vue b/src/views/miniapp/index.vue new file mode 100644 index 0000000..0f5ca44 --- /dev/null +++ b/src/views/miniapp/index.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/src/views/system/admin/components/AdminForm.vue b/src/views/system/admin/components/AdminForm.vue new file mode 100644 index 0000000..78d192d --- /dev/null +++ b/src/views/system/admin/components/AdminForm.vue @@ -0,0 +1,135 @@ + + + diff --git a/src/views/system/admin/components/AdminResetPwdDialog.vue b/src/views/system/admin/components/AdminResetPwdDialog.vue new file mode 100644 index 0000000..e432603 --- /dev/null +++ b/src/views/system/admin/components/AdminResetPwdDialog.vue @@ -0,0 +1,73 @@ + + + diff --git a/src/views/system/admin/components/AdminRoleDialog.vue b/src/views/system/admin/components/AdminRoleDialog.vue new file mode 100644 index 0000000..bd747e3 --- /dev/null +++ b/src/views/system/admin/components/AdminRoleDialog.vue @@ -0,0 +1,82 @@ + + + diff --git a/src/views/system/admin/index.vue b/src/views/system/admin/index.vue new file mode 100644 index 0000000..bf3ac66 --- /dev/null +++ b/src/views/system/admin/index.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/src/views/system/permission/components/PermissionForm.vue b/src/views/system/permission/components/PermissionForm.vue new file mode 100644 index 0000000..8fd1ae0 --- /dev/null +++ b/src/views/system/permission/components/PermissionForm.vue @@ -0,0 +1,191 @@ + + + diff --git a/src/views/system/permission/index.vue b/src/views/system/permission/index.vue new file mode 100644 index 0000000..ed5e253 --- /dev/null +++ b/src/views/system/permission/index.vue @@ -0,0 +1,156 @@ + + + + + diff --git a/src/views/system/role/components/RoleForm.vue b/src/views/system/role/components/RoleForm.vue new file mode 100644 index 0000000..77dc12e --- /dev/null +++ b/src/views/system/role/components/RoleForm.vue @@ -0,0 +1,119 @@ + + + diff --git a/src/views/system/role/components/RolePermissionDialog.vue b/src/views/system/role/components/RolePermissionDialog.vue new file mode 100644 index 0000000..1eb7484 --- /dev/null +++ b/src/views/system/role/components/RolePermissionDialog.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue new file mode 100644 index 0000000..53a2a10 --- /dev/null +++ b/src/views/system/role/index.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/src/views/system/user/components/UserForm.vue b/src/views/system/user/components/UserForm.vue index 6d355c5..fbb68c9 100644 --- a/src/views/system/user/components/UserForm.vue +++ b/src/views/system/user/components/UserForm.vue @@ -14,54 +14,36 @@ const formRef = ref() const formData = reactive({ - username: '', nickname: '', - mobile: '', - email: '', + avatar: '', status: UserStatusEnum.ENABLED, - roleNames: [], }) - const title = computed(() => (formData.id ? '编辑用户' : '新增用户')) - const rules: FormRules = { - username: [ - { required: true, message: '请输入用户名', trigger: 'blur' }, - { min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }, - ], nickname: [{ required: true, message: '请输入昵称', trigger: 'blur' }], - mobile: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }], - email: [{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }], status: [{ required: true, message: '请选择状态', trigger: 'change' }], } function resetForm() { Object.assign(formData, { id: undefined, - username: '', nickname: '', - mobile: '', - email: '', + avatar: '', status: UserStatusEnum.ENABLED, - roleNames: [], }) void nextTick(() => formRef.value?.clearValidate()) } - async function open(id?: number) { + async function open(id: number) { visible.value = true resetForm() - if (!id) return const detail = await UserAPI.detail(id) Object.assign(formData, { id: detail.id, - username: detail.username, nickname: detail.nickname, - mobile: detail.mobile, - email: detail.email, + avatar: detail.avatar, status: detail.status, - roleNames: detail.roleNames ?? [], }) } @@ -73,8 +55,6 @@ try { if (formData.id) { await UserAPI.update(formData.id, formData) - } else { - await UserAPI.create(formData) } ElMessage.success('保存成功') visible.value = false @@ -88,19 +68,16 @@