diff --git a/components.d.ts b/components.d.ts index b00dff2..ea02460 100644 --- a/components.d.ts +++ b/components.d.ts @@ -14,22 +14,34 @@ declare module 'vue' { export interface GlobalComponents { ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] + ElButton: typeof import('element-plus/es')['ElButton'] + ElCard: typeof import('element-plus/es')['ElCard'] ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] + ElDialog: typeof import('element-plus/es')['ElDialog'] ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] + ElForm: typeof import('element-plus/es')['ElForm'] + ElFormItem: typeof import('element-plus/es')['ElFormItem'] + ElInput: typeof import('element-plus/es')['ElInput'] ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] + ElOption: typeof import('element-plus/es')['ElOption'] + ElPagination: typeof import('element-plus/es')['ElPagination'] ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] + ElSelect: typeof import('element-plus/es')['ElSelect'] ElSlider: typeof import('element-plus/es')['ElSlider'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] + ElTable: typeof import('element-plus/es')['ElTable'] + ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] + ElTag: typeof import('element-plus/es')['ElTag'] Icon: typeof import('./src/components/icon/icon.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] @@ -42,28 +54,43 @@ declare module 'vue' { TySidebar: typeof import('./src/components/ty-sidebar.vue')['default'] TyTabs: typeof import('./src/components/ty-tabs.vue')['default'] } + export interface GlobalDirectives { + vLoading: typeof import('element-plus/es')['ElLoadingDirective'] + } } // For TSX support declare global { const ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] const ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] + const ElButton: typeof import('element-plus/es')['ElButton'] + const ElCard: typeof import('element-plus/es')['ElCard'] const ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] const ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] + const ElDialog: typeof import('element-plus/es')['ElDialog'] const ElDrawer: typeof import('element-plus/es')['ElDrawer'] const ElDropdown: typeof import('element-plus/es')['ElDropdown'] const ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] const ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] + const ElForm: typeof import('element-plus/es')['ElForm'] + const ElFormItem: typeof import('element-plus/es')['ElFormItem'] + const ElInput: typeof import('element-plus/es')['ElInput'] const ElMenu: typeof import('element-plus/es')['ElMenu'] const ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] + const ElOption: typeof import('element-plus/es')['ElOption'] + const ElPagination: typeof import('element-plus/es')['ElPagination'] const ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] const ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] const ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] + const ElSelect: typeof import('element-plus/es')['ElSelect'] const ElSlider: typeof import('element-plus/es')['ElSlider'] const ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] const ElSwitch: typeof import('element-plus/es')['ElSwitch'] + const ElTable: typeof import('element-plus/es')['ElTable'] + const ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] const ElTabPane: typeof import('element-plus/es')['ElTabPane'] const ElTabs: typeof import('element-plus/es')['ElTabs'] + const ElTag: typeof import('element-plus/es')['ElTag'] const Icon: typeof import('./src/components/icon/icon.vue')['default'] const RouterLink: typeof import('vue-router')['RouterLink'] const RouterView: typeof import('vue-router')['RouterView'] diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 0000000..a4afc5f --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,139 @@ +/** + * 用户管理数据源。 + * TODO: 对接后端后,把下面 mock 实现替换为 http 调用(字段与返回形状保持不变)。 + * 后端 User 实体字段:id/tenantCode/username/nickname/email/phone/status/orgCode/createdAt + */ + +export type UserStatus = 'enabled' | 'disabled' | 'locked' + +export interface User { + id: string + tenantCode: string + username: string + nickname: string | null + email: string | null + phone: string | null + status: UserStatus + orgCode: string | null + createdAt: number +} + +export interface UserListQuery { + page: number + pageSize: number + username?: string + nickname?: string + phone?: string + status?: UserStatus | '' +} + +export interface UserListResult { + list: User[] + total: number + page: number + pageSize: number +} + +export interface UserUpsertData { + username: string + nickname?: string + email?: string + phone?: string + status?: UserStatus + orgCode?: string | null + password?: string +} + +/** 占位组织(后续接后端换真实组织数据)。 */ +export const mockOrgs: { code: string; name: string }[] = [ + { code: 'touyun', name: '投云总部' }, + { code: 'rd', name: '研发部' }, + { code: 'ops', name: '运维部' }, + { code: 'sales', name: '销售部' }, +] + +const now = Date.now() +const day = 24 * 60 * 60 * 1000 + +function mockUser(id: string, username: string, nickname: string, orgCode: string, status: UserStatus, daysAgo: number): User { + return { + id, + tenantCode: 'touyun', + username, + nickname, + email: `${username}@example.com`, + phone: `1380000${String(1000 + Number(id)).slice(-4)}`, + status, + orgCode, + createdAt: now - daysAgo * day, + } +} + +let mockUsers: User[] = [ + mockUser('1', 'touyun', '超级管理员', 'touyun', 'enabled', 30), + mockUser('2', 'zhangsan', '张三', 'rd', 'enabled', 20), + mockUser('3', 'lisi', '李四', 'rd', 'disabled', 18), + mockUser('4', 'wangwu', '王五', 'ops', 'locked', 15), + mockUser('5', 'zhaoliu', '赵六', 'sales', 'enabled', 12), + mockUser('6', 'sunqi', '孙七', 'ops', 'enabled', 9), + mockUser('7', 'zhouba', '周八', 'rd', 'enabled', 7), + mockUser('8', 'wujiu', '吴九', 'sales', 'disabled', 5), + mockUser('9', 'zhengshi', '郑十', 'rd', 'enabled', 3), + mockUser('10', 'chenyi', '陈一', 'sales', 'enabled', 1), +] + +function uuid(): string { + return crypto.randomUUID() +} + +export function orgName(code: string | null): string { + if (!code) return '-' + return mockOrgs.find((o) => o.code === code)?.name ?? code +} + +export async function listUsers(query: UserListQuery): Promise { + const { page, pageSize, username, nickname, phone, status } = query + const filtered = mockUsers.filter((u) => { + if (username && !u.username.includes(username)) return false + if (nickname && !(u.nickname ?? '').includes(nickname)) return false + if (phone && !(u.phone ?? '').includes(phone)) return false + if (status && u.status !== status) return false + return true + }) + const total = filtered.length + const start = (page - 1) * pageSize + return { list: filtered.slice(start, start + pageSize), total, page, pageSize } +} + +export async function createUser(data: UserUpsertData): Promise { + const user: User = { + id: uuid(), + tenantCode: 'touyun', + username: data.username, + nickname: data.nickname ?? null, + email: data.email ?? null, + phone: data.phone ?? null, + status: data.status ?? 'enabled', + orgCode: data.orgCode ?? null, + createdAt: Date.now(), + } + mockUsers.unshift(user) + return user +} + +export async function updateUser(id: string, data: Partial): Promise { + const user = mockUsers.find((u) => u.id === id) + if (!user) throw new Error('用户不存在') + Object.assign(user, data) + return user +} + +export async function deleteUser(id: string): Promise { + mockUsers = mockUsers.filter((u) => u.id !== id) + return null +} + +export async function resetPassword(_id: string, _password: string): Promise { + // mock:无真实密码存储,仅占位 + return null +} diff --git a/src/pages/system/user/index.vue b/src/pages/system/user/index.vue new file mode 100644 index 0000000..8e288fd --- /dev/null +++ b/src/pages/system/user/index.vue @@ -0,0 +1,295 @@ + + + + + diff --git a/stats.html b/stats.html index 27fa5c9..aff1e11 100644 --- a/stats.html +++ b/stats.html @@ -4930,7 +4930,7 @@ var drawChart = (function (exports) {