-
是否更新已经存在的用户数据
-
下载模板
+
重新加载
+
+
+ {{ scope.row.username }}当前账号
+
+
+ {{ roleName(scope.row.roleId) }}
+
+ {{ parseTime(scope.row.createdAt) }}
+
+ 编辑
+ 重置密码
+ 删除
+
+
+
+
+
{{ user.username }}当前账号
+
- 昵称
- {{ user.nickName || '-' }}
- 角色
- {{ roleName(user.roleId) }}
- 状态
- {{ user.status === '2' ? '正常' : '停用' }}
+
+ 编辑
+ 重置密码
+ 删除
-
提示:仅允许导入"xls"或"xlsx"格式文件!
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ 正常停用
+
+
+ 取消保存
+
+
+
+
diff --git a/admin-ui/tests/e2e/system-management.spec.ts b/admin-ui/tests/e2e/system-management.spec.ts
new file mode 100644
index 0000000..6a3be20
--- /dev/null
+++ b/admin-ui/tests/e2e/system-management.spec.ts
@@ -0,0 +1,92 @@
+import { expect, test } from '@playwright/test'
+
+const baseURL = process.env.ADMIN_UI_URL || 'http://127.0.0.1:9528'
+
+const group = (title: string, path: string, children: object[]) => ({
+ menuName: path.split('/').pop(),
+ title,
+ icon: 'ri:settings-3-line',
+ path,
+ component: 'Layout',
+ visible: '0',
+ children
+})
+
+const page = (title: string, path: string, component: string) => ({
+ menuName: path.split('/').pop(), title, path, component, visible: '0', children: []
+})
+
+const navigation = [
+ group('生成配置', '/chorus/configuration', [
+ page('上游服务商', '/chorus/providers', 'chorus/providers/index'),
+ page('模型配置', '/chorus/models', 'chorus/models/index'),
+ page('路由策略', '/chorus/routes', 'chorus/routes/index'),
+ page('提示词模板', '/chorus/templates', 'chorus/templates/index')
+ ]),
+ group('运行监控', '/chorus/monitoring', [
+ page('上游健康', '/chorus/health', 'chorus/health/index'),
+ page('生成记录', '/chorus/generations', 'chorus/generations/index')
+ ]),
+ group('用户与访问', '/chorus/access', [
+ page('终端用户', '/chorus/users', 'chorus/users/index'),
+ page('API 密钥', '/chorus/api-keys', 'chorus/api-keys/index')
+ ]),
+ group('系统管理', '/chorus/system', [
+ page('管理员账号', '/chorus/system/admins', 'admin/sys-user/index'),
+ page('角色权限', '/chorus/system/roles', 'admin/sys-role/index'),
+ page('菜单结构', '/chorus/system/menus', 'admin/sys-menu/index'),
+ page('接口清单', '/chorus/system/apis', 'admin/sys-api/index'),
+ page('登录日志', '/chorus/system/login-logs', 'admin/sys-login-log/index')
+ ])
+]
+
+test.beforeEach(async ({ context, page: browserPage }) => {
+ await context.addCookies([{ name: 'Admin-Token', value: 'test-token', domain: '127.0.0.1', path: '/' }])
+ await browserPage.route('**/api/v1/**', async route => {
+ const url = new URL(route.request().url())
+ let data: unknown = {}
+ if (url.pathname.endsWith('/getinfo')) {
+ data = { roles: ['admin'], permissions: ['*:*:*'], name: '本地管理员', avatar: '', introduction: '', userId: 1 }
+ } else if (url.pathname.endsWith('/menurole')) {
+ data = navigation
+ } else if (url.pathname.endsWith('/sys-user')) {
+ data = { list: [{ userId: 1, username: 'local-admin', nickName: '本地管理员', roleId: 1, status: '2' }], count: 1 }
+ } else if (url.pathname.endsWith('/role')) {
+ data = { list: [{ roleId: 1, roleName: 'Chorus Operator', roleKey: 'chorus_operator', roleSort: 100, status: '2' }], count: 1 }
+ } else if (url.pathname.endsWith('/menu')) {
+ data = navigation
+ } else if (url.pathname.endsWith('/sys-api') || url.pathname.endsWith('/sys-login-log')) {
+ data = { list: [], count: 0 }
+ }
+ await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 200, data }) })
+ })
+})
+
+test('renders four groups and all thirteen confirmed entries', async ({ page: browserPage }) => {
+ await browserPage.goto(`${baseURL}/#/chorus/system/admins`)
+ await expect(browserPage.getByRole('heading', { name: '管理员账号' })).toBeVisible()
+ for (const title of ['生成配置', '运行监控', '用户与访问', '系统管理']) {
+ await expect(browserPage.getByText(title, { exact: true }).first()).toBeVisible()
+ }
+ for (const title of navigation.flatMap(item => item.children.map(child => child.title))) {
+ await expect(browserPage.getByText(title, { exact: true }).first()).toBeAttached()
+ }
+ await expect(browserPage.getByText('当前账号', { exact: true }).first()).toBeVisible()
+ await browserPage.screenshot({ path: 'test-results/system-admin-desktop.png', fullPage: true })
+})
+
+test('keeps system inventory pages read-only on desktop and narrow screens', async ({ page: browserPage }) => {
+ for (const [path, heading] of [['menus', '菜单结构'], ['apis', '接口清单'], ['login-logs', '登录日志']]) {
+ await browserPage.goto(`${baseURL}/#/chorus/system/${path}`)
+ await expect(browserPage.getByRole('heading', { name: heading })).toBeVisible()
+ await expect(browserPage.getByText('只读', { exact: true })).toBeVisible()
+ await expect(browserPage.getByRole('button', { name: /新增|编辑|删除/ })).toHaveCount(0)
+ }
+
+ await browserPage.setViewportSize({ width: 390, height: 844 })
+ await browserPage.goto(`${baseURL}/#/chorus/system/admins`)
+ await expect(browserPage.getByRole('heading', { name: '管理员账号' })).toBeVisible()
+ const overflow = await browserPage.evaluate(() => document.documentElement.scrollWidth > document.documentElement.clientWidth)
+ expect(overflow).toBeFalsy()
+ await browserPage.screenshot({ path: 'test-results/system-admin-narrow.png', fullPage: true })
+})
diff --git a/admin-ui/tests/unit/admin/system-management.spec.js b/admin-ui/tests/unit/admin/system-management.spec.js
new file mode 100644
index 0000000..319fe08
--- /dev/null
+++ b/admin-ui/tests/unit/admin/system-management.spec.js
@@ -0,0 +1,39 @@
+import fs from 'fs'
+import path from 'path'
+
+const read = relativePath => fs.readFileSync(path.resolve(__dirname, `../../../${relativePath}`), 'utf8')
+
+describe('Chorus system management pages', () => {
+ test.each([
+ ['sys-menu', '菜单结构'],
+ ['sys-api', '接口清单'],
+ ['sys-login-log', '登录日志']
+ ])('%s is explicitly read-only and exposes no write action', (view, heading) => {
+ const source = read(`src/views/admin/${view}/index.vue`)
+ expect(source).toContain(`
${heading}
`)
+ expect(source).toContain('>只读')
+ expect(source).not.toMatch(/handle(Add|Update|Delete)/)
+ expect(source).not.toMatch(/新增|编辑|删除[^。]/)
+ })
+
+ test('read-only API modules only issue GET requests', () => {
+ for (const api of ['sys-menu', 'sys-api', 'sys-login-log']) {
+ const source = read(`src/api/admin/${api}.js`)
+ expect(source).not.toMatch(/method: '(post|put|delete)'/)
+ }
+ })
+
+ test('administrator page omits legacy organization and contact dependencies', () => {
+ const source = read('src/views/admin/sys-user/index.vue')
+ expect(source).toContain('
管理员账号
')
+ expect(source).toContain('当前账号')
+ expect(source).not.toMatch(/部门|岗位|手机号|邮箱|sys-dept|sys-post/)
+ })
+
+ test('role page presents the built-in role protection boundary', () => {
+ const source = read('src/views/admin/sys-role/index.vue')
+ expect(source).toContain("const PROTECTED_ROLE_KEY = 'chorus_operator'")
+ expect(source).toContain('内置保护')
+ expect(source).not.toMatch(/数据权限|sys-dept/)
+ })
+})