diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 435ab24..540b7a8 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/package.json b/package.json index 81ac179..65f42b3 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "eslint-plugin-n": "^16.6.2", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-vue": "^9.24.0", - "msw": "^2.3.0", + "msw": "^2.3.5", + "pinia-plugin-persistedstate": "^3.2.1", "postcss": "^8.4.38", "typescript": "^5.4.4" }, diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index bdfdb11..15751fa 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -8,7 +8,7 @@ * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.3.0' +const PACKAGE_VERSION = '2.3.5' const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() diff --git a/quasar.config.cjs b/quasar.config.cjs index 85a391c..94b7165 100644 --- a/quasar.config.cjs +++ b/quasar.config.cjs @@ -43,21 +43,21 @@ module.exports = configure(function (ctx) { // https://github.com/quasarframework/quasar/tree/dev/extras extras: [ // 'ionicons-v4', - // 'mdi-v5', + 'mdi-v7', // 'fontawesome-v6', // 'eva-icons', // 'themify', // 'line-awesome', // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! - 'roboto-font', // optional, you are not bound to it - 'material-symbols-rounded' // optional, you are not bound to it + 'roboto-font' // optional, you are not bound to it + // 'material-symbols-rounded' // optional, you are not bound to it ], // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build build: { target: { - browser: ['es2022', 'edge88', 'firefox115', 'chrome115', 'safari14'], + browser: ['es2022', 'edge115', 'firefox115', 'chrome115', 'safari14'], node: 'node20' }, @@ -125,7 +125,7 @@ module.exports = configure(function (ctx) { } }, - iconSet: 'material-symbols-rounded', // Quasar icon set + iconSet: 'mdi-v7', // Quasar icon set lang: 'en-US', // Quasar language pack // For special cases outside of where the auto-import strategy can have an impact @@ -136,7 +136,7 @@ module.exports = configure(function (ctx) { // directives: [], // Quasar plugins - plugins: ['Notify', 'SessionStorage'] + plugins: ['Notify', 'Cookies'] }, // animations: 'all', // --- includes all animations diff --git a/src/api/privileges.ts b/src/api/privileges.ts new file mode 100644 index 0000000..b9d8018 --- /dev/null +++ b/src/api/privileges.ts @@ -0,0 +1,5 @@ +import { api } from 'boot/axios' + +export const retrievePrivilegeTree = (username: string) => { + return api.get(`/privileges/${username}/tree`) +} diff --git a/src/boot/i18n.ts b/src/boot/i18n.ts index 2808281..52daaf2 100644 --- a/src/boot/i18n.ts +++ b/src/boot/i18n.ts @@ -1,11 +1,15 @@ import { boot } from 'quasar/wrappers' import { createI18n } from 'vue-i18n' -import messages from 'src/i18n' +import { messages } from 'src/i18n' + +import { useLocaleStore } from 'stores/locale-store' + +const localeStore = useLocaleStore() export default boot(({ app }) => { const i18n = createI18n({ - locale: 'en-US', legacy: false, + locale: localeStore.lang || 'zh-CN', messages }) diff --git a/src/boot/router.ts b/src/boot/router.ts index 1eaf5f5..f5d2090 100644 --- a/src/boot/router.ts +++ b/src/boot/router.ts @@ -1,15 +1,40 @@ import { boot } from 'quasar/wrappers' import { useUserStore } from 'stores/user-store' +import { Cookies } from 'quasar' +import type { RouteRecordRaw } from 'vue-router' +import type { PrivilegeTreeNode } from 'src/models' export default boot(({ router, store }) => { router.beforeEach((to, from, next) => { // Now you need to add your authentication logic here, like calling an API endpoint const userStore = useUserStore(store) - if (userStore.getUsername) { + if (userStore.user?.username && Cookies.get('logged_in')) { if (to.path === '/login') { - next('/') + next({ path: '/' }) } else { - next() + // 获取权限,注册路由表 + if (to.path !== '/' && !userStore.routes.length) { + const routes = generateRoutes(userStore.privileges as PrivilegeTreeNode[]) + + // 动态添加可访问路由表 + userStore.updateRoutes(routes) + routes.forEach((route) => { + router.addRoute(route as RouteRecordRaw) + }) + // 捕获所有未匹配的路径,放在配置的末尾 + router.addRoute({ + path: '/:cacheAll(.*)*', + name: 'ErrorNotFound', + component: () => import('src/pages/ErrorNotFound.vue') + }) + + const redirectPath = from.query.redirect || to.path + const redirect = decodeURIComponent(redirectPath as string) + const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect } + next(nextData) + } else { + next() + } } } else { if (to.path === '/login') { @@ -20,3 +45,38 @@ export default boot(({ router, store }) => { } }) }) + +// 生成路由 +const MainLayout = () => import('src/layouts/MainLayout.vue') +const BlankLayout = () => import('src/layouts/BlankLayout.vue') + +const modules = import.meta.glob('../pages/**/*.{vue,tsx}') + +export const generateRoutes = (routes: PrivilegeTreeNode[]): RouteRecordRaw[] => { + const res: RouteRecordRaw[] = [] + for (const route of routes) { + const data: RouteRecordRaw = { + path: route.path as string, + name: route.name, + redirect: route.redirect, + component: null, + children: [] + } + if (route.component) { + const comModule = modules[`../${route.component}.vue`] + const component = route.component as string + if (comModule) { + // 动态加载路由文件 + data.component = comModule + } else if (component.includes('#')) { + data.component = component === '#' ? MainLayout : BlankLayout + } + } + // recursive child routes + if (route.children) { + data.children = generateRoutes(route.children) + } + res.push(data) + } + return res +} diff --git a/src/components/EssentialLink.vue b/src/components/EssentialLink.vue index 0239589..7f96015 100644 --- a/src/components/EssentialLink.vue +++ b/src/components/EssentialLink.vue @@ -1,24 +1,28 @@ diff --git a/src/components/EssentialList.vue b/src/components/EssentialList.vue new file mode 100644 index 0000000..b553578 --- /dev/null +++ b/src/components/EssentialList.vue @@ -0,0 +1,27 @@ + + + diff --git a/src/components/LanguageSelector.vue b/src/components/LanguageSelector.vue new file mode 100644 index 0000000..f319176 --- /dev/null +++ b/src/components/LanguageSelector.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/components/ThemeToogle.vue b/src/components/ThemeToogle.vue new file mode 100644 index 0000000..5ecead8 --- /dev/null +++ b/src/components/ThemeToogle.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/css/app.scss b/src/css/app.scss index 329d8c0..e21ba5d 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -5,17 +5,21 @@ } .bg-negative-gradient { - background: radial-gradient(circle at center bottom, #e05969, $negative); + background: radial-gradient(circle at center left, $red-6, $negative); } .bg-positive-gradient { - background: radial-gradient(circle at center right, #26A69A, $positive); + background: radial-gradient(circle at center right, $light-green, $positive); } .bg-warning-gradient { - background: radial-gradient(circle at bottom left, #cf9900, $warning); + background: radial-gradient(circle at center top, $amber, $warning); } .rounded-full { border-radius: 50%; +} + +.bg-dark-page { + background-color: var(--q-dark-page); } \ No newline at end of file diff --git a/src/i18n/en-US/index.ts b/src/i18n/en-US/index.ts index 728ecff..fb30029 100644 --- a/src/i18n/en-US/index.ts +++ b/src/i18n/en-US/index.ts @@ -2,16 +2,7 @@ // so you can safely delete all default props below export default { - application: 'Application', - reload: 'Reload Data', - pagation: 'The records in rows {page} - {size} ,of {total} total.', - deletion: 'Do you want to confirm the deletion?', - note: 'This information will be deleted and will not be recovered once deleted.', - viewMore: 'View More', - unreadTotal: 'unread total', - search: 'Search...', - select: 'Plesse Select', profile: 'Profile', settings: 'Settings', help: 'Help', @@ -20,10 +11,6 @@ export default { signup: 'Sign Up', signout: 'Sign Out', - accesslogs: 'Access Logs', - account: 'Account Settings', - security: 'Account Security', - no: 'NO.', name: 'Name', id: 'ID', @@ -35,6 +22,7 @@ export default { password: 'Password', firstname: 'First Name', lastname: 'Last Name', + fullname: 'Full Name', accountLocked: 'Account Locked', accountExpiresAt: 'User Expires At', credentialsExpiresAt: 'Credentials Expires At', @@ -47,22 +35,6 @@ export default { areaCode: 'Area Code', postalCode: 'Postal Code', - userCount: 'User Count', - roleCount: 'Role Count', - postsCount: 'Posts Count', - - unread: 'Unread', - readed: 'Readed', - - title: 'Title', - context: 'Context', - cover: 'Cover', - tags: 'Tags', - category: 'Category', - viewed: 'Viewed', - likes: 'Likes', - comments: 'Comments', - downloads: 'Downloads', enabled: 'Is Enabled', enable: 'Enable', @@ -74,7 +46,7 @@ export default { confirm: 'Confirm', commit: 'Commit', cancle: 'Cancle', - add: 'Add New', + add: 'Add', edit: 'Edit', delete: 'Delete', unlock: 'Unlock', @@ -82,19 +54,18 @@ export default { members: 'memberss', home: 'Home', - dashboard: 'Dashboard', system: 'System', groups: 'Groups', roles: 'Roles', - components: 'Components', + privileges: 'Privileges', users: 'Users', dictionaries: 'Dictionaries', - posts: 'Posts', - categories: 'Categroies', regions: 'Regions', - statitics: 'Statitics', - messages: 'Notifications', - status: 'Status', + logs: 'Logs', + operationLog: 'Operation Log', + accessLog: 'Access Log', + auditLog: 'Audit Log', + schedulerLog: 'Scheduler Log', welcome: 'Hi! Welcome Back.', subtitle: 'Please sign in to continue exploring.', diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 63b26bd..2c050c9 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -2,7 +2,13 @@ import enUS from './en-US' import zhCN from './zh-CN' import zhTW from './zh-TW' -export default { +export const localeOptions = [ + { value: 'en-US', label: 'English(US)' }, + { value: 'zh-CN', label: '中文(简体)' }, + { value: 'zh-TW', label: '中文(繁體)' } +] + +export const messages = { 'en-US': enUS, 'zh-CN': zhCN, 'zh-TW': zhTW diff --git a/src/i18n/zh-CN/index.ts b/src/i18n/zh-CN/index.ts index 8c4b2bd..b9110f6 100644 --- a/src/i18n/zh-CN/index.ts +++ b/src/i18n/zh-CN/index.ts @@ -2,16 +2,7 @@ // so you can safely delete all default props below export default { - application: '系统', - reload: '重载数据', - pagation: '第 {page} - {size} 行记录,共 {total} 条记录', - deletion: '是否确认删除?', - note: '将要删除本条信息,一旦删除将不可恢复。', - viewMore: '查看更多', - unreadTotal: '未读消息', - search: '搜索...', - select: '请选择', profile: '个人中心', settings: '设置', help: '帮助', @@ -20,10 +11,6 @@ export default { signup: '注 册', signout: '退出登录', - accesslogs: '操作日志', - account: '账号设置', - security: '账号安全', - no: '序号', name: '名称', id: '主键', @@ -35,6 +22,7 @@ export default { password: '密码', firstname: '姓', lastname: '名字', + fullname: '姓名', accountLocked: '账号锁状态', accountExpiresAt: '账号失效时间', credentialsExpiresAt: '密码失效时间', @@ -47,26 +35,8 @@ export default { areaCode: '区号', postalCode: '邮编', - userCount: '用户数', - roleCount: '角色数', - postsCount: '贴子数', - - unread: '未读', - readed: '已读', - - title: '标题', - cover: '封面', - tags: '标签', - context: '内容', - category: '分类', - viewed: '浏览量', - likes: '点赞数', - comments: '评论数', - downloads: '下载量', enabled: '是否启用', - enable: '启用', - disable: '禁用', actions: '操作', import: '导入', @@ -82,19 +52,18 @@ export default { members: '成员', home: '首页', - dashboard: '控制台', system: '系统管理', groups: '分组', roles: '角色', - components: '权限', + privileges: '权限', users: '用户', dictionaries: '字典', - posts: '帖子管理', - categories: '类目管理', regions: '行政区划', - statitics: '数据统计', - messages: '消息通知', - status: '状态', + logs: '日志记录', + operationLog: '操作日志', + accessLog: '访问日志', + auditLog: '审计日志', + schedulerLog: '调度日志', welcome: '嗨!欢迎回来', subtitle: '请填写您的账号和密码,让我们继续探索', diff --git a/src/i18n/zh-TW/index.ts b/src/i18n/zh-TW/index.ts index 98109fc..ef4a3d4 100644 --- a/src/i18n/zh-TW/index.ts +++ b/src/i18n/zh-TW/index.ts @@ -2,16 +2,7 @@ // so you can safely delete all default props below export default { - application: '系統', - reload: '重載數據', - pagation: '第 {page} - {size} 行記錄,共 {total} 條記錄', - deletion: '是否確認刪除?', - note: '將要刪除本條信息,一旦刪除將不可恢復。', - viewMore: '查看更多', - unreadTotal: '未讀消息', - search: '搜索...', - select: '請選擇', profile: '個人中心', settings: '設置', help: '幫助', @@ -20,10 +11,6 @@ export default { signup: '註 冊', signout: '退出登錄', - accesslogs: '操作日誌', - account: '賬號設置', - security: '賬號安全', - no: '序號', name: '名稱', id: '主鍵', @@ -35,6 +22,7 @@ export default { password: '密碼', firstname: '姓', lastname: '名字', + fullname: '姓名', accountLocked: '賬號鎖狀態', accountExpiresAt: '賬號失效時間', credentialsExpiresAt: '密碼失效時間', @@ -47,22 +35,6 @@ export default { areaCode: '區號', postalCode: '郵編', - userCount: '用戶數', - roleCount: '角色數', - postsCount: '貼子數', - - unread: '未讀', - readed: '已讀', - - title: '標題', - cover: '封面', - tags: '標籤', - context: '內容', - category: '分類', - viewed: '瀏覽量', - likes: '點贊數', - comments: '評論數', - downloads: '下載量', enabled: '是否啟用', enable: '啟用', @@ -82,19 +54,18 @@ export default { members: '成員', home: '首頁', - dashboard: '控制台', system: '系統管理', groups: '分組', roles: '角色', - components: '權限', + privileges: '權限', users: '用戶', dictionaries: '字典', - posts: '帖子管理', - categories: '類目管理', regions: '行政區劃', - statitics: '數據統計', - messages: '消息通知', - status: '狀態', + logs: '日誌管理', + accessLog: '訪問紀錄', + operationLog: '操作紀錄', + auditLog: '審計日誌', + schedulerLog: '調度日誌', welcome: '嗨!歡迎回來', subtitle: '請填寫您的帳號和密碼,讓我們繼續探索', diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 36100a7..1f4d5ab 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -1,52 +1,31 @@ diff --git a/src/layouts/SideBarLeft.vue b/src/layouts/SideBarLeft.vue deleted file mode 100644 index c7588ec..0000000 --- a/src/layouts/SideBarLeft.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - diff --git a/src/mocks/common.ts b/src/mocks/common.ts index 0e8a479..9a514c4 100644 --- a/src/mocks/common.ts +++ b/src/mocks/common.ts @@ -6,7 +6,11 @@ export const commonHandlers = [ const username = info.get('username') // Read the intercepted request body. - return HttpResponse.json({ username }) + return HttpResponse.json({ user: { username, avatar: '' }, access_token: 'sfa23asdfasdfasdf' }, { + headers: { + 'Set-Cookie': 'logged_in=yes' + } + }) }), http.post('/api/logout', ({ cookies }) => { if (!cookies.logged_in) { diff --git a/src/mocks/dictionaries.ts b/src/mocks/dictionaries.ts index fb29785..fe174c5 100644 --- a/src/mocks/dictionaries.ts +++ b/src/mocks/dictionaries.ts @@ -8,12 +8,12 @@ for (let i = 0; i < 20; i++) { const data: Dictionary = { id: i, name: 'dictionary_' + i, - description: 'this is description for this row', enabled: i % 3 > 0, + description: 'This is dictionary description about xxx', lastModifiedDate: new Date() } for (let j = 0; j < i; j++) { - const data: Dictionary = { + const subData: Dictionary = { id: j, name: 'dictionary_' + i + '_' + j, superiorId: i, @@ -21,7 +21,7 @@ for (let i = 0; i < 20; i++) { description: 'description', lastModifiedDate: new Date() } - subDatas.push(data) + subDatas.push(subData) } datas.push(data) } diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 9250de0..c90a952 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -4,6 +4,7 @@ import { groupsHandlers } from './groups' import { regionsHandlers } from './regions' import { rolesHandlers } from './roles' import { usersHandlers } from './users' +import { privilegessHandlers } from './privileges' export const handlers = [ ...commonHandlers, @@ -11,5 +12,6 @@ export const handlers = [ ...groupsHandlers, ...regionsHandlers, ...rolesHandlers, - ...usersHandlers + ...usersHandlers, + ...privilegessHandlers ] diff --git a/src/mocks/privileges.ts b/src/mocks/privileges.ts index 5a8b057..c558f57 100644 --- a/src/mocks/privileges.ts +++ b/src/mocks/privileges.ts @@ -1,24 +1,251 @@ import { http, HttpResponse } from 'msw' -import type { Privilege } from 'src/models' +import type { Privilege, PrivilegeTreeNode } from 'src/models' -const datas: Privilege[] = [] +const datas: Privilege[] = [ + { + id: 1, + path: '/system', + component: '#', + redirect: '/system/users', + name: 'system', + order: 1, + enabled: true, + icon: 'mdi-cog-outline', + description: 'this is description for this row' + }, + { + id: 7, + path: '/logs', + component: '#', + redirect: '/logs/operation', + name: 'logs', + order: 2, + enabled: true, + icon: 'mdi-clipboard-list-outline', + description: 'this is description for this row' + } +] + +const subDatas: Privilege[] = [ + { + id: 2, + superiorId: 1, + path: 'groups', + component: 'pages/system/groups/IndexPage', + name: 'groups', + order: 1, + enabled: true, + icon: 'mdi-account-multiple-plus-outline', + description: 'this is description for this row' + }, + { + id: 3, + superiorId: 1, + path: 'users', + component: 'pages/system/users/IndexPage', + name: 'users', + order: 2, + enabled: true, + icon: 'mdi-account-outline', + description: 'this is description for this row' + }, + { + id: 4, + superiorId: 1, + path: 'privileges', + component: 'pages/system/privileges/IndexPage', + name: 'privileges', + order: 3, + enabled: true, + icon: 'mdi-shield-key-outline', + description: 'this is description for this row' + }, + { + id: 5, + superiorId: 1, + path: 'roles', + component: 'pages/system/roles/IndexPage', + name: 'roles', + order: 4, + enabled: true, + icon: 'mdi-shield-account-outline', + description: 'this is description for this row' + }, + { + id: 6, + superiorId: 1, + path: 'dictionaries', + component: 'pages/system/dictionaries/IndexPage', + name: 'dictionaries', + order: 5, + enabled: true, + icon: 'mdi-book-outline', + description: 'this is description for this row' + }, + { + id: 8, + superiorId: 7, + path: 'operation', + component: 'pages/logs/operation/IndexPage', + name: 'operationLog', + order: 1, + enabled: true, + icon: 'mdi-clipboard-text-outline', + description: 'this is description for this row' + }, + { + id: 9, + superiorId: 7, + path: 'access', + component: 'pages/logs/access/IndexPage', + name: 'accessLog', + order: 2, + enabled: true, + icon: 'mdi-file-document-outline', + description: 'this is description for this row' + }, + { + id: 10, + superiorId: 7, + path: 'audit', + component: 'pages/logs/audit/IndexPage', + name: 'auditLog', + order: 3, + enabled: true, + icon: 'mdi-clipboard-check-outline', + description: 'this is description for this row' + }, + { + id: 11, + superiorId: 7, + path: 'scheduler', + component: 'pages/logs/scheduler/IndexPage', + name: 'schedulerLog', + order: 4, + enabled: true, + icon: 'mdi-calendar-text-outline', + description: 'this is description for this row' + } +] -for (let i = 0; i < 20; i++) { - const data: Privilege = { - id: i, - name: 'privilege_' + i, - superiorId: i, - meta: { - icon: 'user' - }, - enabled: i % 3 > 0, - description: 'description', - lastModifiedDate: new Date() +const treeNodes: PrivilegeTreeNode[] = [ + { + id: 1, + path: '/system', + component: '#', + redirect: '/system/users', + name: 'system', + order: 1, + icon: 'mdi-cog-outline', + children: [ + { + id: 2, + path: 'groups', + component: 'pages/system/groups/IndexPage', + name: 'groups', + order: 1, + icon: 'mdi-account-multiple-plus-outline' + }, + { + id: 3, + path: 'users', + component: 'pages/system/users/IndexPage', + name: 'users', + order: 2, + icon: 'mdi-account-outline' + }, + { + id: 4, + path: 'privileges', + component: 'pages/system/privileges/IndexPage', + name: 'privileges', + order: 3, + icon: 'mdi-shield-key-outline' + }, + { + id: 5, + path: 'roles', + component: 'pages/system/roles/IndexPage', + name: 'roles', + order: 4, + icon: 'mdi-shield-account-outline' + }, + { + id: 6, + path: 'dictionaries', + component: 'pages/system/dictionaries/IndexPage', + name: 'dictionaries', + order: 5, + icon: 'mdi-book-outline' + } + ] + }, + { + id: 7, + path: '/logs', + component: '#', + redirect: '/logs/operation', + name: 'logs', + order: 2, + icon: 'mdi-clipboard-list-outline', + children: [ + { + id: 8, + path: 'operation', + component: 'pages/logs/operation/IndexPage', + name: 'operationLog', + order: 1, + icon: 'mdi-clipboard-text-outline' + }, + { + id: 9, + path: 'access', + component: 'pages/logs/access/IndexPage', + name: 'accessLog', + order: 2, + icon: 'mdi-file-document-outline' + }, + { + id: 10, + path: 'audit', + component: 'pages/logs/audit/IndexPage', + name: 'auditLog', + order: 3, + icon: 'mdi-clipboard-check-outline' + }, + { + id: 11, + path: 'scheduler', + component: 'pages/logs/scheduler/IndexPage', + name: 'schedulerLog', + order: 4, + icon: 'mdi-calendar-text-outline' + } + ] } - datas.push(data) -} +] -export const rolesHandlers = [ +export const privilegessHandlers = [ + http.get('/api/privileges/:username/tree', ({ params }) => { + const { username } = params + console.log(username) + return HttpResponse.json(treeNodes) + }), + http.get('/api/privileges/:id/subset', ({ params }) => { + const { id } = params + return HttpResponse.json(subDatas.filter(item => item.superiorId === Number(id))) + }), + http.get('/api/privileges/:id', ({ params }) => { + const { id } = params + if (id) { + let res = datas.filter(item => item.id === Number(id)) + if (!res) { + res = subDatas.filter(item => item.id === Number(id)) + } + return HttpResponse.json(res) + } + return HttpResponse.json(null) + }), http.get('/api/privileges', ({ request }) => { const url = new URL(request.url) const page = url.searchParams.get('page') diff --git a/src/mocks/regions.ts b/src/mocks/regions.ts index 9931d03..6ead250 100644 --- a/src/mocks/regions.ts +++ b/src/mocks/regions.ts @@ -2,21 +2,76 @@ import { http, HttpResponse } from 'msw' import type { Region } from 'src/models' const datas: Region[] = [] +const subDatas: Region[] = [] -for (let i = 0; i < 20; i++) { +for (let i = 0; i < 34; i++) { const data: Region = { id: i, name: 'region_' + i, - areaCode: i, - postalCode: i, + areaCode: (11 + i) * 10000, + postalCode: (11 + i), enabled: i % 3 > 0, - description: 'description', + description: 'This is region description about xxx', lastModifiedDate: new Date() } + for (let j = 0; j < i; j++) { + const subData: Region = { + id: j, + name: 'region_' + i + '_' + j, + superiorId: i, + areaCode: data.areaCode + j, + postalCode: data.postalCode + j, + enabled: j % 2 > 0, + description: 'This is region description about xxx', + lastModifiedDate: new Date() + } + subDatas.push(subData) + } datas.push(data) } export const regionsHandlers = [ + http.get('/api/regions/:id/subset', ({ params, request }) => { + const superiorId = params.id + + const url = new URL(request.url) + const page = url.searchParams.get('page') + const size = url.searchParams.get('size') + + // sort and filter + const sortBy = url.searchParams.get('sortBy') as never + if (sortBy) { + datas.sort((a: Region, b: Region) => { + if (a[sortBy] > b[sortBy]) { + return 1 + } + if (a[sortBy] < b[sortBy]) { + return -1 + } + return 0 + }) + } + let data = { + } + + const filter = url.searchParams.get('filter') + if (filter) { + const result = subDatas.filter(item => item.superiorId === Number(superiorId)).filter(item => item.name.includes(filter)).slice(Number(page) * Number(size), (Number(page) + 1) * Number(size)) + data = { + content: result, + totalElements: result.length + } + return HttpResponse.json(data) + } + // Construct a JSON response with the list of all Dictionarys + // as the response body. + data = { + content: Array.from(subDatas.filter(item => item.superiorId === Number(superiorId)).slice(Number(page) * Number(size), (Number(page) + 1) * Number(size))), + totalElements: datas.length + } + + return HttpResponse.json(data) + }), http.get('/api/regions', ({ request }) => { const url = new URL(request.url) const page = url.searchParams.get('page') diff --git a/src/mocks/roles.ts b/src/mocks/roles.ts index 2af0300..b045a4a 100644 --- a/src/mocks/roles.ts +++ b/src/mocks/roles.ts @@ -8,7 +8,7 @@ for (let i = 0; i < 20; i++) { id: i, name: 'role_' + i, enabled: i % 3 > 0, - description: 'description', + description: 'This is role description about xxx', lastModifiedDate: new Date() } datas.push(data) diff --git a/src/mocks/users.ts b/src/mocks/users.ts index f08ab3f..ccb5125 100644 --- a/src/mocks/users.ts +++ b/src/mocks/users.ts @@ -11,8 +11,8 @@ for (let i = 0; i < 20; i++) { lastname: 'lastname_' + i, enabled: i % 2 > 0, accountNonLocked: i % 3 > 0, - accountExpiresAt: new Date(), - credentialsExpiresAt: new Date(), + accountExpiresAt: new Date(2024, 7, i), + credentialsExpiresAt: new Date(2024, i, 3), lastModifiedDate: new Date() } datas.push(data) diff --git a/src/models.d.ts b/src/models.d.ts index 75375e8..9ebcff7 100644 --- a/src/models.d.ts +++ b/src/models.d.ts @@ -30,10 +30,11 @@ export interface Privilege extends AudtiMetadata { name: string superiorId?: number path?: string - meta: { - icon?: string - actions?: string[] - } + redirect?: string + component?: string + icon?: string + actions?: string[] + order: number enabled?: boolean description?: string } @@ -53,3 +54,20 @@ export interface Region extends AudtiMetadata { enabled?: boolean description?: string } + +export interface TreeNode { + id: number + name: string + superiorId?: number + children?: TreeNode[] +} + +export interface PrivilegeTreeNode extends TreeNode { + path?: string + redirect?: string + component: string + icon?: string + actions?: string[] + order: number + children?: PrivilegeTreeNode[] +} diff --git a/src/pages/IndexPage.vue b/src/pages/IndexPage.vue index c3fa73a..7c1481b 100644 --- a/src/pages/IndexPage.vue +++ b/src/pages/IndexPage.vue @@ -1,10 +1,10 @@ diff --git a/src/pages/LoginPage.vue b/src/pages/LoginPage.vue index 46d3f4d..ceb9587 100644 --- a/src/pages/LoginPage.vue +++ b/src/pages/LoginPage.vue @@ -1,42 +1,28 @@ diff --git a/src/pages/system/group/IndexPage.vue b/src/pages/system/groups/IndexPage.vue similarity index 75% rename from src/pages/system/group/IndexPage.vue rename to src/pages/system/groups/IndexPage.vue index a1b73ed..7c5246d 100644 --- a/src/pages/system/group/IndexPage.vue +++ b/src/pages/system/groups/IndexPage.vue @@ -1,7 +1,7 @@