Files
portal-mini-front/mock/index.ts
T
2026-10-27 23:44:33 +08:00

336 lines
10 KiB
TypeScript

import {
files,
menuRoutes,
miniApps,
type MockFile,
users,
USER_STATUS,
type MockMiniApp,
} from './data'
export interface MockContext {
url: string
params: string[]
query: Record<string, string>
body: Record<string, unknown>
}
export interface MockRule {
method: 'GET' | 'POST' | 'PUT' | 'DELETE'
pattern: RegExp
handler: (ctx: MockContext) => unknown
/** 全局 mock 关闭时仍生效(用于单独 mock 新增接口) */
alwaysMock?: boolean
}
const toNumber = (value: unknown, fallback = 0) => {
const n = Number(value)
return Number.isFinite(n) ? n : fallback
}
const pickString = (value: unknown, fallback = '') => (typeof value === 'string' ? value : fallback)
export const mockRules: MockRule[] = [
{
method: 'POST',
pattern: /^\/api\/auth\/login$/,
handler: (ctx) => ({
accessToken: 'mock-access-token',
refreshToken: 'mock-refresh-token',
tokenType: 'Bearer',
expiresIn: 7200,
username: pickString(ctx.body.username, 'admin'),
}),
},
{
method: 'POST',
pattern: /^\/api\/auth\/logout$/,
handler: () => true,
},
{
method: 'GET',
pattern: /^\/api\/auth\/me$/,
handler: () => ({
userId: 1,
username: 'admin',
nickname: '超级管理员',
avatar: '',
roles: ['ADMIN'],
permissions: [
'sys:user:add',
'sys:user:edit',
'sys:user:delete',
'sys:user:query',
'miniapp:add',
'miniapp:edit',
'miniapp:delete',
'miniapp:query',
],
}),
},
{
method: 'GET',
pattern: /^\/api\/auth\/routes$/,
handler: () => menuRoutes,
},
{
method: 'GET',
pattern: /^\/api\/api\/admin\/users$/,
handler: (ctx) => {
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) => {
if (miniId && item.mini_id !== miniId) return false
if (keyword && !item.nickname.includes(keyword) && !item.openid.includes(keyword)) return false
return true
})
const start = (page - 1) * size
return { list: filtered.slice(start, start + size), total: filtered.length }
},
},
{
method: 'GET',
pattern: /^\/api\/api\/admin\/users\/(\d+)$/,
handler: (ctx) => users.find((item) => item.id === toNumber(ctx.params[0])) ?? null,
},
{
method: 'PUT',
pattern: /^\/api\/api\/admin\/users\/(\d+)$/,
handler: (ctx) => {
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\/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\/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\/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\/api\/admin\/miniapps$/,
handler: (ctx) => {
const id = miniApps.length + 1
const created: MockMiniApp = {
id,
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,
ext: pickString(ctx.body.ext, '{}'),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}
miniApps.unshift(created)
return id
},
},
{
method: 'PUT',
pattern: /^\/api\/api\/admin\/miniapps\/(\d+)$/,
handler: (ctx) => {
const target = miniApps.find((item) => item.id === toNumber(ctx.params[0]))
if (!target) return false
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\/api\/admin\/miniapps\/([\d,]+)$/,
handler: (ctx) => {
const ids = ctx.params[0].split(',').map((id) => toNumber(id))
ids.forEach((id) => {
const index = miniApps.findIndex((item) => item.id === id)
if (index > -1) miniApps.splice(index, 1)
})
return true
},
},
{
method: 'GET',
pattern: /^\/api\/api\/admin\/files$/,
handler: (ctx) => {
const { keyword, uploader_type, page, size } = ctx.query
const pageNum = toNumber(page, 1)
const pageSize = toNumber(size, 20)
const kw = (keyword as string) || ''
const type = (uploader_type as string) || ''
const filtered = files.filter((item) => {
if (type && item.uploader_type !== type) return false
if (kw && !item.origin_name.includes(kw) && !item.filename.includes(kw)) return false
return true
})
const start = (pageNum - 1) * pageSize
return { list: filtered.slice(start, start + pageSize), total: filtered.length }
},
},
{
method: 'POST',
pattern: /^\/api\/api\/admin\/files\/upload$/,
handler: () => {
const id = files.length + 1
const rand = Math.random().toString(36).slice(2, 10)
const created: MockFile = {
id,
storage_key: `2026/10/27/${rand}.png`,
filename: `${rand}.png`,
origin_name: 'mock-upload.png',
mime_type: 'image/png',
ext: 'png',
size: 1024,
uploader_type: 'admin',
uploader_id: 1,
url: `/files/static/2026/10/27/${rand}.png`,
created_at: new Date().toISOString().slice(0, 19).replace('T', ' '),
}
files.unshift(created)
return {
id,
url: created.url,
filename: created.filename,
origin_name: created.origin_name,
mime_type: created.mime_type,
size: created.size,
}
},
},
{
method: 'DELETE',
pattern: /^\/api\/api\/admin\/files\/(\d+)$/,
handler: (ctx) => {
const id = toNumber(ctx.params[0])
const index = files.findIndex((item) => item.id === id)
if (index > -1) files.splice(index, 1)
return { id }
},
},
{
// 统计每个小程序的用户数(有真实用户的取真实值,其余返回稳定模拟值)
method: 'GET',
pattern: /^\/api\/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\/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\/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(),
}
},
},
]