permission
This commit is contained in:
+57
-32
@@ -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 },
|
||||
],
|
||||
}
|
||||
|
||||
+178
-34
@@ -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<string, unknown>
|
||||
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<string, unknown>
|
||||
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(),
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
+5
-4
@@ -12,15 +12,15 @@ function readBody(req: NodeJS.ReadableStream): Promise<string> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地 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()
|
||||
|
||||
Reference in New Issue
Block a user