diff --git a/orion-ops-ui/package-lock.json b/orion-ops-ui/package-lock.json index 2711edaf..1776517e 100644 --- a/orion-ops-ui/package-lock.json +++ b/orion-ops-ui/package-lock.json @@ -10,6 +10,8 @@ "license": "Apache 2.0", "dependencies": { "@arco-design/web-vue": "^2.44.7", + "@dangojs/a-query-header": "^0.0.31", + "@sanqi377/arco-vue-icon-picker": "^1.0.7", "@vueuse/core": "^9.3.0", "axios": "^0.24.0", "dayjs": "^1.11.5", @@ -1077,6 +1079,27 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@dangojs/a-query-header": { + "version": "0.0.31", + "resolved": "https://registry.npmmirror.com/@dangojs/a-query-header/-/a-query-header-0.0.31.tgz", + "integrity": "sha512-hXLAU8oIQ1ULDY+qXKoKstqo3/m4HTjXcxNaFbPatTVMHwox4tBAIc5Nox1crvlWkNagUyk7PpnQuzLH73j9AQ==", + "peerDependencies": { + "@arco-design/web-vue": "^2.47.1", + "@dangojs/digitforce-ui-utils": "^0.0.9", + "lodash": "^4.17.21", + "vue": "^3.3.2" + } + }, + "node_modules/@dangojs/digitforce-ui-utils": { + "version": "0.0.9", + "resolved": "https://registry.npmmirror.com/@dangojs/digitforce-ui-utils/-/digitforce-ui-utils-0.0.9.tgz", + "integrity": "sha512-ujmlm/dHteE0+EPuMWQLcGt68BtA4uiZYdlCvpc6tyYOw4cNWKZ94bleBuL+qjxzvnyj04aDpz1dq8wiyre7WQ==", + "peer": true, + "dependencies": { + "lodash": "^4.17.21", + "vue": "^3.2.47" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.15.18", "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz", @@ -1457,6 +1480,14 @@ } } }, + "node_modules/@sanqi377/arco-vue-icon-picker": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/@sanqi377/arco-vue-icon-picker/-/arco-vue-icon-picker-1.0.7.tgz", + "integrity": "sha512-KxafhphhK63UW/cNb7I/aowDFhRyu5UvxQkrs60z/EaEbAqHgRIxOJKIaPmm1K6K5moU8GSMY3u6DdfB4aiDRg==", + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/@sindresorhus/is": { "version": "0.7.0", "resolved": "https://registry.npmmirror.com/@sindresorhus/is/-/is-0.7.0.tgz", diff --git a/orion-ops-ui/package.json b/orion-ops-ui/package.json index 214a3634..7fcf30bf 100644 --- a/orion-ops-ui/package.json +++ b/orion-ops-ui/package.json @@ -30,6 +30,8 @@ }, "dependencies": { "@arco-design/web-vue": "^2.44.7", + "@dangojs/a-query-header": "^0.0.31", + "@sanqi377/arco-vue-icon-picker": "^1.0.7", "@vueuse/core": "^9.3.0", "axios": "^0.24.0", "dayjs": "^1.11.5", diff --git a/orion-ops-ui/src/api/system/menu.ts b/orion-ops-ui/src/api/system/menu.ts new file mode 100644 index 00000000..981671ce --- /dev/null +++ b/orion-ops-ui/src/api/system/menu.ts @@ -0,0 +1,78 @@ +import axios from 'axios'; + +/** + * 菜单创建请求 + */ +export interface MenuCreateRequest { + parentId?: number; + name?: string; + permission?: string; + type?: number; + sort?: number; + cache?: number; + icon?: string; + path?: string; + component?: string; +} + +/** + * 菜单更新请求 + */ +export interface MenuUpdateRequest extends MenuCreateRequest { + id: number; +} + +/** + * 菜单查询请求 + */ +export interface MenuQueryRequest { + name?: string; + status?: number; +} + +/** + * 菜单查询响应 + */ +export interface MenuQueryResponse { + id?: number; + parentId?: number; + name?: string; + permission?: string; + type?: number; + sort?: number; + visible?: number; + status?: number; + cache?: number; + icon?: string; + path?: string; + component?: string; + children?: Array; +} + +/** + * 查询菜单列表 + */ +export function getMenuList(request?: MenuQueryRequest) { + return axios.post('/infra/system-menu/list', request); +} + +/** + * 创建菜单 + */ +export function createMenu(request: MenuCreateRequest) { + return axios.post('/infra/system-menu/create', request); +} + +/** + * 修改菜单 + */ +export function updateMenu(request: MenuUpdateRequest) { + return axios.put('/infra/system-menu/update', request); +} + +/** + * 删除菜单 + */ +export function deleteMenu(id: number) { + return axios.delete('/infra/system-menu/delete', { params: { id } }); +} diff --git a/orion-ops-ui/src/api/user/auth.ts b/orion-ops-ui/src/api/user/auth.ts index c22670b6..b7402ee7 100644 --- a/orion-ops-ui/src/api/user/auth.ts +++ b/orion-ops-ui/src/api/user/auth.ts @@ -1,11 +1,17 @@ import axios from 'axios'; import { UserState } from '@/store/modules/user/types'; +/** + * 登陆请求 + */ export interface LoginRequest { username: string; password: string; } +/** + * 登陆响应 + */ export interface LoginResponse { token: string; } diff --git a/orion-ops-ui/src/assets/style/global.less b/orion-ops-ui/src/assets/style/global.less index 00c9c218..f4598470 100644 --- a/orion-ops-ui/src/assets/style/global.less +++ b/orion-ops-ui/src/assets/style/global.less @@ -17,11 +17,9 @@ body { } .echarts-tooltip-diy { - background: linear-gradient( - 304.17deg, + background: linear-gradient(304.17deg, rgba(253, 254, 255, 0.6) -6.04%, - rgba(244, 247, 252, 0.6) 85.2% - ) !important; + rgba(244, 247, 252, 0.6) 85.2%) !important; border: none !important; backdrop-filter: blur(10px) !important; /* Note: backdrop-filter has minimal browser support */ @@ -75,12 +73,20 @@ body { & > .arco-card-header { height: auto; - padding: 20px; + padding: 16px; border: none; } & > .arco-card-body { - padding: 0 20px 20px 20px; + padding: 0 16px 16px 16px; + } +} + +.a-query-header-grid { + .arco-grid-item { + &:last-child { + display: flex !important; + } } } diff --git a/orion-ops-ui/src/assets/style/layout.less b/orion-ops-ui/src/assets/style/layout.less index 7cd3312f..9abda707 100644 --- a/orion-ops-ui/src/assets/style/layout.less +++ b/orion-ops-ui/src/assets/style/layout.less @@ -2,4 +2,102 @@ background-color: var(--color-fill-2); padding: 16px 16px 0 16px; display: flex; + flex-direction: column; +} + +.table-search-card { + width: 100%; + margin-bottom: 16px; +} + +.table-card { + width: 100%; +} + +.table-wrapper { + margin-top: 16px; +} + +.table-option-wrapper { + .arco-btn-text { + padding: 0 8px; + } +} + +.ml4 { + margin-left: 4px; +} + +.mr4 { + margin-right: 4px; +} + +.mt4 { + margin-top: 4px; +} + +.mb4 { + margin-bottom: 4px; +} + +.mx4 { + margin-left: 4px; + margin-right: 4px; +} + +.my4 { + margin-top: 4px; + margin-bottom: 4px; +} + +.ml8 { + margin-left: 8px; +} + +.mr8 { + margin-right: 8px; +} + +.mt8 { + margin-top: 8px; +} + +.mb8 { + margin-bottom: 8px; +} + +.mx8 { + margin-left: 8px; + margin-right: 8px; +} + +.my8 { + margin-top: 8px; + margin-bottom: 8px; +} + +.ml16 { + margin-left: 16px; +} + +.mr16 { + margin-right: 16px; +} + +.mt16 { + margin-top: 16px; +} + +.mb16 { + margin-bottom: 16px; +} + +.mx16 { + margin-left: 16px; + margin-right: 16px; +} + +.my16 { + margin-top: 16px; + margin-bottom: 16px; } diff --git a/orion-ops-ui/src/components/index.ts b/orion-ops-ui/src/components/index.ts index e61aa5ef..ef2c7d7a 100644 --- a/orion-ops-ui/src/components/index.ts +++ b/orion-ops-ui/src/components/index.ts @@ -1,5 +1,6 @@ import { App } from 'vue'; import { use } from 'echarts/core'; +import AQueryHeader from '@dangojs/a-query-header'; import { CanvasRenderer } from 'echarts/renderers'; import { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts'; import { @@ -29,5 +30,6 @@ export default { install(Vue: App) { Vue.component('Chart', Chart); Vue.component('Breadcrumb', Breadcrumb); + Vue.component('a-query-header', AQueryHeader); }, }; diff --git a/orion-ops-ui/src/hooks/user.ts b/orion-ops-ui/src/hooks/user.ts index c34a7404..75780bf9 100644 --- a/orion-ops-ui/src/hooks/user.ts +++ b/orion-ops-ui/src/hooks/user.ts @@ -10,7 +10,7 @@ export default function useUser() { await userStore.logout(); const currentRoute = router.currentRoute.value; Message.success('已退出登录'); - router.push({ + await router.push({ name: logoutTo || 'login', query: { ...router.currentRoute.value.query, diff --git a/orion-ops-ui/src/router/routes/base.ts b/orion-ops-ui/src/router/routes/base.ts index 409b0011..07d751fc 100644 --- a/orion-ops-ui/src/router/routes/base.ts +++ b/orion-ops-ui/src/router/routes/base.ts @@ -55,7 +55,7 @@ export const REDIRECT_ROUTER: RouteRecordRaw = { export const FORBIDDEN_ROUTE: RouteRecordRaw = { path: '/:pathMatch(.*)*', name: FORBIDDEN_ROUTER_NAME, - component: () => import('@/views/base/forbidden/index.vue'), + component: () => import('@/views/exception/forbidden/index.vue'), }; /** @@ -64,7 +64,7 @@ export const FORBIDDEN_ROUTE: RouteRecordRaw = { export const NOT_FOUND_ROUTE: RouteRecordRaw = { path: '/:pathMatch(.*)*', name: NOT_FOUND_ROUTER_NAME, - component: () => import('@/views/base/not-found/index.vue'), + component: () => import('@/views/exception/not-found/index.vue'), }; export default [ diff --git a/orion-ops-ui/src/router/routes/modules/system.menu.ts b/orion-ops-ui/src/router/routes/modules/system.menu.ts new file mode 100644 index 00000000..513950b5 --- /dev/null +++ b/orion-ops-ui/src/router/routes/modules/system.menu.ts @@ -0,0 +1,17 @@ +import { DEFAULT_LAYOUT } from '../base'; +import { AppRouteRecordRaw } from '../types'; + +const MENU: AppRouteRecordRaw = { + name: 'menu', + path: '/system', + component: DEFAULT_LAYOUT, + children: [ + { + name: 'systemMenu', + path: '/system/menu', + component: () => import('@/views/system/menu/index.vue'), + }, + ], +}; + +export default MENU; diff --git a/orion-ops-ui/src/store/index.ts b/orion-ops-ui/src/store/index.ts index d74af3c3..646bbc14 100644 --- a/orion-ops-ui/src/store/index.ts +++ b/orion-ops-ui/src/store/index.ts @@ -2,13 +2,15 @@ import { createPinia } from 'pinia'; import useAppStore from './modules/app'; import useUserStore from './modules/user'; import useTabBarStore from './modules/tab-bar'; +import useMenuStore from './modules/system/menu'; const pinia = createPinia(); export { useAppStore, useUserStore, - useTabBarStore + useTabBarStore, + useMenuStore }; export default pinia; diff --git a/orion-ops-ui/src/store/modules/system/menu/index.ts b/orion-ops-ui/src/store/modules/system/menu/index.ts new file mode 100644 index 00000000..4b783571 --- /dev/null +++ b/orion-ops-ui/src/store/modules/system/menu/index.ts @@ -0,0 +1,80 @@ +import { defineStore } from 'pinia'; +import { MenuState } from './types'; +import { MenuQueryResponse } from '@/api/system/menu'; +import { TreeNodeData } from '@arco-design/web-vue'; + +const useMenuStore = defineStore('menu', { + state: (): MenuState => ({ + menus: [] + }), + + getters: { + menuState(state: MenuState): MenuState { + return { ...state }; + }, + treeData(state: MenuState): TreeNodeData[] { + let render = (arr: any[]): TreeNodeData[] => { + return arr.map((s) => { + // 非 function + if (s.type === 3) { + return null as unknown as TreeNodeData; + } + // 当前节点 + const node = { + key: s.id, + title: s.name, + children: undefined as unknown + } as TreeNodeData; + // 子节点 + if (s.children && s.children.length) { + node.children = render(s.children); + } + return node; + }).filter(Boolean); + }; + return render([{ name: '根目录', id: 0 }, ...state.menus]); + } + }, + + actions: { + /** + * 更新菜单 + */ + updateMenu(menus: MenuQueryResponse[]) { + this.menus = menus; + }, + + /** + * 获取父菜单 + */ + findParentMenu(arr: any, id: number): any { + if (!arr || !arr.length) { + return null; + } + // 当前级 + for (let e of arr) { + if (e.id === id) { + return arr; + } + } + // 子级 + for (let e of arr) { + if (e.children && e.children.length) { + // @ts-ignore + if (this.findParent(e.children, id) !== null) { + return e.children; + } + } + } + }, + + /** + * 清空菜单 + */ + reset() { + this.menus = []; + }, + }, +}); + +export default useMenuStore; diff --git a/orion-ops-ui/src/store/modules/system/menu/types.ts b/orion-ops-ui/src/store/modules/system/menu/types.ts new file mode 100644 index 00000000..d2e92f5d --- /dev/null +++ b/orion-ops-ui/src/store/modules/system/menu/types.ts @@ -0,0 +1,7 @@ +import { MenuQueryResponse } from '@/api/system/menu'; + +export interface MenuState { + menus: MenuQueryResponse[], + + [key: string]: unknown; +} diff --git a/orion-ops-ui/src/types/global.ts b/orion-ops-ui/src/types/global.ts index 9e7a32e9..d28b5d4a 100644 --- a/orion-ops-ui/src/types/global.ts +++ b/orion-ops-ui/src/types/global.ts @@ -24,8 +24,11 @@ export interface PostData { } export interface Pagination { - page: number; - limit: number; + page?: number; + limit?: number; + current?: number; + pageSize?: number; + total?: number; } export type TimeRanger = [string, string]; diff --git a/orion-ops-ui/src/utils/enum.ts b/orion-ops-ui/src/utils/enum.ts new file mode 100644 index 00000000..9183c992 --- /dev/null +++ b/orion-ops-ui/src/utils/enum.ts @@ -0,0 +1,37 @@ +/** + * 转为 select options + */ +export const toOptions = (enums: any) => { + const arr = []; + for (let k in enums) { + arr.push(enums[k]); + } + return arr; +}; + +/** + * 获取枚举值 + */ +export const getEnumValue = (value: any, + enums: any, + key = 'label', + defaultValue = value) => { + for (let k in enums) { + if (enums[k].value === value) { + return enums[k][key]; + } + } + return defaultValue; +}; + +/** + * 获取枚举对象 + */ +export const getEnum = (value: any, enums: any) => { + for (let k in enums) { + if (enums[k].value === value) { + return enums[k]; + } + } + return {}; +}; diff --git a/orion-ops-ui/src/views/base/not-found/index.vue b/orion-ops-ui/src/views/base/not-found/index.vue deleted file mode 100644 index 08ce3927..00000000 --- a/orion-ops-ui/src/views/base/not-found/index.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - - - diff --git a/orion-ops-ui/src/views/exception/forbidden/index.vue b/orion-ops-ui/src/views/exception/forbidden/index.vue new file mode 100644 index 00000000..ceb212ed --- /dev/null +++ b/orion-ops-ui/src/views/exception/forbidden/index.vue @@ -0,0 +1,36 @@ + + + + + + + diff --git a/orion-ops-ui/src/views/base/forbidden/index.vue b/orion-ops-ui/src/views/exception/not-found/index.vue similarity index 50% rename from orion-ops-ui/src/views/base/forbidden/index.vue rename to orion-ops-ui/src/views/exception/not-found/index.vue index f7461197..534370c4 100644 --- a/orion-ops-ui/src/views/base/forbidden/index.vue +++ b/orion-ops-ui/src/views/exception/not-found/index.vue @@ -1,8 +1,9 @@ @@ -11,8 +12,14 @@ import { useRouter } from 'vue-router'; const router = useRouter(); - const back = () => { - router.push({ name: 'workplace' }); + const to = (name: string) => { + router.push({ name: name }); + }; + + + diff --git a/orion-ops-ui/src/views/system/menu/components/menu-form-modal.vue b/orion-ops-ui/src/views/system/menu/components/menu-form-modal.vue new file mode 100644 index 00000000..c3432359 --- /dev/null +++ b/orion-ops-ui/src/views/system/menu/components/menu-form-modal.vue @@ -0,0 +1,202 @@ + + + + + + + diff --git a/orion-ops-ui/src/views/system/menu/components/menu-table.vue b/orion-ops-ui/src/views/system/menu/components/menu-table.vue new file mode 100644 index 00000000..99f2b9ac --- /dev/null +++ b/orion-ops-ui/src/views/system/menu/components/menu-table.vue @@ -0,0 +1,249 @@ + + + + + + + diff --git a/orion-ops-ui/src/views/system/menu/components/menu-tree-selector.vue b/orion-ops-ui/src/views/system/menu/components/menu-tree-selector.vue new file mode 100644 index 00000000..1210584f --- /dev/null +++ b/orion-ops-ui/src/views/system/menu/components/menu-tree-selector.vue @@ -0,0 +1,39 @@ + + + + + + +; diff --git a/orion-ops-ui/src/views/system/menu/index.vue b/orion-ops-ui/src/views/system/menu/index.vue new file mode 100644 index 00000000..ce103902 --- /dev/null +++ b/orion-ops-ui/src/views/system/menu/index.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/orion-ops-ui/src/views/system/menu/type/enum.types.ts b/orion-ops-ui/src/views/system/menu/type/enum.types.ts new file mode 100644 index 00000000..cb4423eb --- /dev/null +++ b/orion-ops-ui/src/views/system/menu/type/enum.types.ts @@ -0,0 +1,63 @@ +/** + * 菜单类型 + */ +export const MenuTypeEnum = { + PARENT_MENU: { + value: 1, + label: '父菜单' + }, + SUB_MENU: { + value: 2, + label: '子菜单' + }, + FUNCTION: { + value: 3, + label: '功能' + } +}; + +/** + * 菜单状态 + */ +export const MenuStatusEnum = { + DISABLED: { + value: 0, + label: '停用', + color: 'orange' + }, + ENABLED: { + value: 1, + label: '启用', + color: 'blue' + } +}; + +/** + * 菜单是否可见 + */ +export const MenuVisibleEnum = { + HIDE: { + value: 0, + label: '隐藏', + color: 'orange' + }, + SHOW: { + value: 1, + label: '显示', + color: 'blue' + } +}; + +/** + * 菜单缓存状态 + */ +export const MenuCacheEnum = { + HIDE: { + value: 0, + label: '不缓存', + }, + SHOW: { + value: 1, + label: '缓存', + } +}; diff --git a/orion-ops-ui/src/views/system/menu/type/form.rules.ts b/orion-ops-ui/src/views/system/menu/type/form.rules.ts new file mode 100644 index 00000000..a778604f --- /dev/null +++ b/orion-ops-ui/src/views/system/menu/type/form.rules.ts @@ -0,0 +1,11 @@ +import { FieldRule } from '@arco-design/web-vue'; + +export const name = [{ + required: true, + +}] as FieldRule[]; + + +export default { + name +} as Record; diff --git a/orion-ops-ui/src/views/system/menu/type/table.columns.ts b/orion-ops-ui/src/views/system/menu/type/table.columns.ts new file mode 100644 index 00000000..1a50670d --- /dev/null +++ b/orion-ops-ui/src/views/system/menu/type/table.columns.ts @@ -0,0 +1,58 @@ +import { TableColumnData } from '@arco-design/web-vue/es/table/interface'; + +const columns = [ + { + title: '菜单名称', + dataIndex: 'menuName', + slotName: 'menuName', + fixed: 'left', + width: 250, + }, { + title: '图标', + dataIndex: 'icon', + slotName: 'icon', + align: 'center', + width: 60, + }, { + title: '类型', + dataIndex: 'type', + slotName: 'type', + width: 80, + }, { + title: '排序', + dataIndex: 'sort', + slotName: 'sort', + width: 70, + }, { + title: '状态', + dataIndex: 'status', + slotName: 'status', + width: 120, + }, { + title: '权限标识', + dataIndex: 'permission', + slotName: 'permission', + ellipsis: true, + tooltip: true + }, { + title: '组件名称', + dataIndex: 'component', + slotName: 'component', + width: 150, + ellipsis: true, + tooltip: true, + }, { + title: '链接路径', + dataIndex: 'path', + slotName: 'path', + ellipsis: true, + tooltip: true, + }, { + title: '操作', + slotName: 'option', + width: 158, + fixed: 'right', + } +] as TableColumnData[]; + +export default columns;