diff --git a/.npmrc b/.npmrc index 0154bc0..86c14a0 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,4 @@ shamefully-hoist=true strict-peer-dependencies=false -shell-emulator=true \ No newline at end of file +shell-emulator=true +registry=https://registry.npmmirror.com/ \ No newline at end of file diff --git a/.vite/deps_temp_399fba7b/package.json b/.vite/deps_temp_399fba7b/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/.vite/deps_temp_399fba7b/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/indexDataScreen.html b/indexDataScreen.html new file mode 100644 index 0000000..2d35f8a --- /dev/null +++ b/indexDataScreen.html @@ -0,0 +1,101 @@ + + + + + + + + + pure-admin-thin + + + + + + + + +
+ +
+
+ + + diff --git a/package.json b/package.json index 019b1c8..8110fed 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "not op_mini all" ], "dependencies": { - "@jiaminghi/data-view": "^2.10.0", + "@kjgl77/datav-vue3": "^1.7.2", "@pureadmin/descriptions": "^1.1.1", "@pureadmin/table": "^2.3.2", "@pureadmin/utils": "^1.9.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f26ea96..45c81a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,9 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: - "@jiaminghi/data-view": - specifier: ^2.10.0 - version: 2.10.0 + "@kjgl77/datav-vue3": + specifier: ^1.7.2 + version: 1.7.2(vue@3.3.4) "@pureadmin/descriptions": specifier: ^1.1.1 version: 1.1.1(element-plus@2.3.6) @@ -1477,16 +1477,6 @@ packages: } dev: false - /@jiaminghi/data-view@2.10.0: - resolution: - { - integrity: sha512-Cud2MTiMcqc5k2KWabR/svuVQmXHANqURo+yj40370/LdI/gyUJ6LG203hWXEnT1nMCeiv/SLVmxv3PXLScCeA== - } - dependencies: - "@babel/runtime": 7.23.9 - "@jiaminghi/charts": 0.2.18 - dev: false - /@jiaminghi/transition@1.1.11: resolution: { @@ -1570,6 +1560,21 @@ packages: "@jridgewell/sourcemap-codec": 1.4.15 dev: true + /@kjgl77/datav-vue3@1.7.2(vue@3.3.4): + resolution: + { + integrity: sha512-Fvllk4rJEdUwLdumvsnPADZNPMKkDdC7u/vJEAPBsMh6UgFQytIT+SVtjj1vy/TXtgcH/te9hlXBPgV8LVSmQw== + } + dependencies: + "@jiaminghi/c-render": 0.4.3 + "@jiaminghi/charts": 0.2.18 + "@jiaminghi/color": 1.1.3 + "@vueuse/core": 10.8.0(vue@3.3.4) + transitivePeerDependencies: + - "@vue/composition-api" + - vue + dev: false + /@nodelib/fs.scandir@2.1.5: resolution: { @@ -1951,6 +1956,13 @@ packages: } dev: false + /@types/web-bluetooth@0.0.20: + resolution: + { + integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow== + } + dev: false + /@typescript-eslint/eslint-plugin@5.60.0(@typescript-eslint/parser@5.60.0)(eslint@8.43.0)(typescript@5.0.4): resolution: { @@ -2391,6 +2403,21 @@ packages: - vue dev: false + /@vueuse/core@10.8.0(vue@3.3.4): + resolution: + { + integrity: sha512-G9Ok9fjx10TkNIPn8V1dJmK1NcdJCtYmDRyYiTMUyJ1p0Tywc1zmOoCQ2xhHYyz8ULBU4KjIJQ9n+Lrty74iVw== + } + dependencies: + "@types/web-bluetooth": 0.0.20 + "@vueuse/metadata": 10.8.0 + "@vueuse/shared": 10.8.0(vue@3.3.4) + vue-demi: 0.14.7(vue@3.3.4) + transitivePeerDependencies: + - "@vue/composition-api" + - vue + dev: false + /@vueuse/core@9.13.0(vue@3.3.4): resolution: { @@ -2413,6 +2440,13 @@ packages: } dev: false + /@vueuse/metadata@10.8.0: + resolution: + { + integrity: sha512-Nim/Vle5OgXcXhAvGOgkJQXB1Yb+Kq/fMbLuv3YYDYbiQrwr39ljuD4k9fPeq4yUyokYRo2RaNQmbbIMWB/9+w== + } + dev: false + /@vueuse/metadata@9.13.0: resolution: { @@ -2455,6 +2489,18 @@ packages: - vue dev: false + /@vueuse/shared@10.8.0(vue@3.3.4): + resolution: + { + integrity: sha512-dUdy6zwHhULGxmr9YUg8e+EnB39gcM4Fe2oKBSrh3cOsV30JcMPtsyuspgFCUo5xxFNaeMf/W2yyKfST7Bg8oQ== + } + dependencies: + vue-demi: 0.14.7(vue@3.3.4) + transitivePeerDependencies: + - "@vue/composition-api" + - vue + dev: false + /@vueuse/shared@9.13.0(vue@3.3.4): resolution: { @@ -9029,6 +9075,24 @@ packages: vue: 3.3.4 dev: false + /vue-demi@0.14.7(vue@3.3.4): + resolution: + { + integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA== + } + engines: { node: ">=12" } + hasBin: true + requiresBuild: true + peerDependencies: + "@vue/composition-api": ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + "@vue/composition-api": + optional: true + dependencies: + vue: 3.3.4 + dev: false + /vue-eslint-parser@9.3.1(eslint@8.43.0): resolution: { diff --git a/src/assets/svg/screenBgCommon.svg b/src/assets/svg/screenBgCommon.svg new file mode 100644 index 0000000..b764dc1 --- /dev/null +++ b/src/assets/svg/screenBgCommon.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/CustomTree/src/collapseTree.tsx b/src/components/CustomTree/src/collapseTree.tsx index 96e8cf6..e491e6f 100644 --- a/src/components/CustomTree/src/collapseTree.tsx +++ b/src/components/CustomTree/src/collapseTree.tsx @@ -1,6 +1,8 @@ import { defineComponent, reactive } from "vue"; import CollapseTreeItem from "./collapseTreeItem"; import "./collapseTreeStyle.scss"; +import styles from "./collapseTreeStyle.scss"; + const testDeviceTreeData = [ { name: "临沂总部", @@ -122,22 +124,27 @@ export default defineComponent({

{vFirst.name}

-
- {Array.isArray(vFirst.childList) && - vFirst.childList.length && ( - - )} -
+ +
+ {Array.isArray(vFirst.childList) && + vFirst.childList.length && ( + + )} +
+
); })} diff --git a/src/layout/dataScreenIndex.vue b/src/layout/dataScreenIndex.vue new file mode 100644 index 0000000..87622c7 --- /dev/null +++ b/src/layout/dataScreenIndex.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/src/pages/dataScreen/App.vue b/src/pages/dataScreen/App.vue new file mode 100644 index 0000000..6dfcbec --- /dev/null +++ b/src/pages/dataScreen/App.vue @@ -0,0 +1,34 @@ + + + + diff --git a/src/pages/dataScreen/main.ts b/src/pages/dataScreen/main.ts new file mode 100644 index 0000000..e0b3add --- /dev/null +++ b/src/pages/dataScreen/main.ts @@ -0,0 +1,72 @@ +/* + * @Author: donghao donghao@supervision.ltd + * @Date: 2024-02-22 14:04:38 + * @LastEditors: donghao donghao@supervision.ltd + * @LastEditTime: 2024-02-22 17:48:10 + * @FilePath: \General-AI-Platform-Web-Client\src\pages\dataScreen\main.ts + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import App from "./App.vue"; +import router from "./router"; +import { setupStore } from "@/store"; +import ElementPlus from "element-plus"; +import { useI18n } from "@/plugins/i18n"; +import { getServerConfig } from "@/config"; +import { createApp, Directive } from "vue"; +import { MotionPlugin } from "@vueuse/motion"; +import { useEcharts } from "@/plugins/echarts"; +import { injectResponsiveStorage } from "@/utils/responsive"; + +import DataVVue3 from "@kjgl77/datav-vue3"; +// import Table from "@pureadmin/table"; +// import PureDescriptions from "@pureadmin/descriptions"; + +// 引入重置样式 +import "@/style/reset.scss"; +// 导入公共样式 +import "@/style/index.scss"; +// 一定要在main.ts中导入tailwind.css,防止vite每次hmr都会请求src/style/index.scss整体css文件导致热更新慢的问题 +import "@/style/tailwind.css"; +import "element-plus/dist/index.css"; +// 导入字体图标 +import "@/assets/iconfont/iconfont.js"; +import "@/assets/iconfont/iconfont.css"; + +const app = createApp(App); + +// 自定义指令 +import * as directives from "@/directives"; +Object.keys(directives).forEach(key => { + app.directive(key, (directives as { [key: string]: Directive })[key]); +}); + +// 全局注册`@iconify/vue`图标库 +import { + IconifyIconOffline, + IconifyIconOnline, + FontIcon +} from "@/components/ReIcon"; +app.component("IconifyIconOffline", IconifyIconOffline); +app.component("IconifyIconOnline", IconifyIconOnline); +app.component("FontIcon", FontIcon); + +// 全局注册按钮级别权限组件 +import { Auth } from "@/components/ReAuth"; +app.component("Auth", Auth); + +getServerConfig(app).then(async config => { + app.use(router); + await router.isReady(); + injectResponsiveStorage(app, config); + setupStore(app); + app + .use(MotionPlugin) + .use(useI18n) + .use(ElementPlus) + .use(useEcharts) + .use(DataVVue3); + // .use(useEcharts); + // .use(Table); + // .use(PureDescriptions); + app.mount("#app"); +}); diff --git a/src/pages/dataScreen/router/index.ts b/src/pages/dataScreen/router/index.ts new file mode 100644 index 0000000..0f37e76 --- /dev/null +++ b/src/pages/dataScreen/router/index.ts @@ -0,0 +1,202 @@ +// import "@/utils/sso"; +import { getConfig } from "@/config"; +import NProgress from "@/utils/progress"; +import { transformI18n } from "@/plugins/i18n"; +import { sessionKey, type DataInfo } from "@/utils/auth"; +import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; +import { usePermissionStoreHook } from "@/store/modules/permission"; +import { + Router, + createRouter, + RouteRecordRaw, + RouteComponent +} from "vue-router"; +import { + ascending, + getTopMenu, + initRouter, + isOneOfArray, + getHistoryMode, + findRouteByPath, + handleAliveRoute, + formatTwoStageRoutes, + formatFlatteningRoutes +} from "./utils"; +import { buildHierarchyTree } from "@/utils/tree"; +import { isUrl, openLink, storageSession, isAllEmpty } from "@pureadmin/utils"; + +import remainingRouter from "./modules/remaining"; + +/** 自动导入全部静态路由,无需再手动引入!匹配 src/router/modules 目录(任何嵌套级别)中具有 .ts 扩展名的所有文件,除了 remaining.ts 文件 + * 如何匹配所有文件请看:https://github.com/mrmlnc/fast-glob#basic-syntax + * 如何排除文件请看:https://cn.vitejs.dev/guide/features.html#negative-patterns + */ +const modules: Record = import.meta.glob( + ["./modules/**/*.ts", "!./modules/**/remaining.ts"], + { + eager: true + } +); + +/** 原始静态路由(未做任何处理) */ +const routes = []; + +Object.keys(modules).forEach(key => { + routes.push(modules[key].default); +}); + +/** 导出处理后的静态路由(三级及以上的路由全部拍成二级) */ +export const constantRoutes: Array = formatTwoStageRoutes( + formatFlatteningRoutes(buildHierarchyTree(ascending(routes.flat(Infinity)))) +); + +/** 用于渲染菜单,保持原始层级 */ +export const constantMenus: Array = ascending( + routes.flat(Infinity) +).concat(...remainingRouter); + +/** 不参与菜单的路由 */ +export const remainingPaths = Object.keys(remainingRouter).map(v => { + return remainingRouter[v].path; +}); + +/** 创建路由实例 */ +export const router: Router = createRouter({ + history: getHistoryMode(import.meta.env.VITE_ROUTER_HISTORY), + routes: constantRoutes.concat(...(remainingRouter as any)), + strict: true, + scrollBehavior(to, from, savedPosition) { + return new Promise(resolve => { + if (savedPosition) { + return savedPosition; + } else { + if (from.meta.saveSrollTop) { + const top: number = + document.documentElement.scrollTop || document.body.scrollTop; + resolve({ left: 0, top }); + } + } + }); + } +}); + +/** 重置路由 */ +export function resetRouter() { + router.getRoutes().forEach(route => { + const { name, meta } = route; + if (name && router.hasRoute(name) && meta?.backstage) { + router.removeRoute(name); + router.options.routes = formatTwoStageRoutes( + formatFlatteningRoutes( + buildHierarchyTree(ascending(routes.flat(Infinity))) + ) + ); + } + }); + usePermissionStoreHook().clearAllCachePage(); +} + +/** 路由白名单 */ +const whiteList = ["/login"]; + +const { VITE_HIDE_HOME } = import.meta.env; + +router.beforeEach((to: ToRouteType, _from, next) => { + if (to.meta?.keepAlive) { + handleAliveRoute(to, "add"); + // 页面整体刷新和点击标签页刷新 + if (_from.name === undefined || _from.name === "Redirect") { + handleAliveRoute(to); + } + } + const userInfo = storageSession().getItem>(sessionKey); + NProgress.start(); + const externalLink = isUrl(to?.name as string); + if (!externalLink) { + to.matched.some(item => { + if (!item.meta.title) return ""; + const Title = getConfig().Title; + if (Title) + document.title = `${transformI18n(item.meta.title)} | ${Title}`; + else document.title = transformI18n(item.meta.title); + }); + } + /** 如果已经登录并存在登录信息后不能跳转到路由白名单,而是继续保持在当前页面 */ + function toCorrectRoute() { + whiteList.includes(to.fullPath) ? next(_from.fullPath) : next(); + } + if (userInfo) { + // 无权限跳转403页面 + if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) { + next({ path: "/error/403" }); + } + // 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面 + if (VITE_HIDE_HOME === "true" && to.fullPath === "/welcome") { + next({ path: "/error/404" }); + } + if (_from?.name) { + // name为超链接 + if (externalLink) { + openLink(to?.name as string); + NProgress.done(); + } else { + toCorrectRoute(); + } + } else { + // 刷新 + if ( + usePermissionStoreHook().wholeMenus.length === 0 && + to.path !== "/login" + ) { + initRouter().then((router: Router) => { + if (!useMultiTagsStoreHook().getMultiTagsCache) { + const { path } = to; + const route = findRouteByPath( + path, + router.options.routes[0].children + ); + getTopMenu(true); + // query、params模式路由传参数的标签页不在此处处理 + if (route && route.meta?.title) { + if (isAllEmpty(route.parentId) && route.meta?.backstage) { + // 此处为动态顶级路由(目录) + const { path, name, meta } = route.children[0]; + useMultiTagsStoreHook().handleTags("push", { + path, + name, + meta + }); + } else { + const { path, name, meta } = route; + useMultiTagsStoreHook().handleTags("push", { + path, + name, + meta + }); + } + } + } + // 确保动态路由完全加入路由列表并且不影响静态路由(注意:动态路由刷新时router.beforeEach可能会触发两次,第一次触发动态路由还未完全添加,第二次动态路由才完全添加到路由列表,如果需要在router.beforeEach做一些判断可以在to.name存在的条件下去判断,这样就只会触发一次) + if (isAllEmpty(to.name)) router.push(to.fullPath); + }); + } + toCorrectRoute(); + } + } else { + if (to.path !== "/login") { + if (whiteList.indexOf(to.path) !== -1) { + next(); + } else { + next({ path: "/login" }); + } + } else { + next(); + } + } +}); + +router.afterEach(() => { + NProgress.done(); +}); + +export default router; diff --git a/src/pages/dataScreen/router/modules/computePowerAllocation.ts b/src/pages/dataScreen/router/modules/computePowerAllocation.ts new file mode 100644 index 0000000..9b8fbb9 --- /dev/null +++ b/src/pages/dataScreen/router/modules/computePowerAllocation.ts @@ -0,0 +1,15 @@ +import { $t } from "@/plugins/i18n"; + +export default { + path: "/computePowerAllocation", + meta: { + title: $t("menus.hsComputePowerAllocation"), + icon: "icon-suanlipeizhi", + // showLink: false, + bodyClass: "computePowerAllocation_page", + rank: 6, + roles: ["admin", "common"] + }, + component: () => import("@/views/computePowerAllocation/index.vue"), + name: "computePowerAllocationIndex" +} as RouteConfigsTable; diff --git a/src/pages/dataScreen/router/modules/remaining.ts b/src/pages/dataScreen/router/modules/remaining.ts new file mode 100644 index 0000000..cbc985a --- /dev/null +++ b/src/pages/dataScreen/router/modules/remaining.ts @@ -0,0 +1,31 @@ +import { $t } from "@/plugins/i18n"; +const Layout = () => import("@/layout/index.vue"); + +export default [ + { + path: "/login", + name: "Login", + component: () => import("@/views/login/index.vue"), + meta: { + title: $t("menus.hslogin"), + showLink: false, + rank: 101 + } + }, + { + path: "/redirect", + component: Layout, + meta: { + title: $t("status.hsLoad"), + showLink: false, + rank: 102 + }, + children: [ + { + path: "/redirect/:path(.*)", + name: "Redirect", + component: () => import("@/layout/redirect.vue") + } + ] + } +] as Array; diff --git a/src/pages/dataScreen/router/modules/workbench.ts b/src/pages/dataScreen/router/modules/workbench.ts new file mode 100644 index 0000000..fa4a304 --- /dev/null +++ b/src/pages/dataScreen/router/modules/workbench.ts @@ -0,0 +1,35 @@ +/* + * @Author: donghao donghao@supervision.ltd + * @Date: 2024-01-12 14:35:28 + * @LastEditors: donghao donghao@supervision.ltd + * @LastEditTime: 2024-02-22 14:28:13 + * @FilePath: \General-AI-Platform-Web-Client\src\router\modules\workbench.ts + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import { $t } from "@/plugins/i18n"; + +const Layout = () => import("@/layout/dataScreenIndex.vue"); + +export default { + path: "/", + name: "Workbench", + component: Layout, + redirect: "/workbench", + meta: { + title: $t("menus.hshome"), + icon: "icon-gongzuotai-weixuan", + // showLink: false, + rank: 1 + }, + children: [ + { + path: "/workbench", + name: "Workbench", + component: () => import("@/pages/dataScreen/views/home/homeIndex.vue"), + meta: { + title: "工作台", + roles: ["admin", "common"] + } + } + ] +} as RouteConfigsTable; diff --git a/src/pages/dataScreen/router/utils.ts b/src/pages/dataScreen/router/utils.ts new file mode 100644 index 0000000..2390b80 --- /dev/null +++ b/src/pages/dataScreen/router/utils.ts @@ -0,0 +1,386 @@ +import { + RouterHistory, + RouteRecordRaw, + RouteComponent, + createWebHistory, + createWebHashHistory +} from "vue-router"; +import { router } from "./index"; +import { isProxy, toRaw } from "vue"; +import { useTimeoutFn } from "@vueuse/core"; +import { + isString, + cloneDeep, + isAllEmpty, + intersection, + storageSession, + isIncludeAllChildren +} from "@pureadmin/utils"; +import { getConfig } from "@/config"; +import { menuType } from "@/layout/types"; +import { buildHierarchyTree } from "@/utils/tree"; +import { sessionKey, type DataInfo } from "@/utils/auth"; +import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; +import { usePermissionStoreHook } from "@/store/modules/permission"; +const IFrame = () => import("@/layout/frameView.vue"); +// https://cn.vitejs.dev/guide/features.html#glob-import +const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}"); + +// 动态路由 +import { getAsyncRoutes } from "@/api/routes"; + +function handRank(routeInfo: any) { + const { name, path, parentId, meta } = routeInfo; + return isAllEmpty(parentId) + ? isAllEmpty(meta?.rank) || + (meta?.rank === 0 && name !== "Workbench" && path !== "/") + ? true + : false + : false; +} + +/** 按照路由中meta下的rank等级升序来排序路由 */ +function ascending(arr: any[]) { + arr.forEach((v, index) => { + // 当rank不存在时,根据顺序自动创建,首页路由永远在第一位 + if (handRank(v)) v.meta.rank = index + 2; + }); + return arr.sort( + (a: { meta: { rank: number } }, b: { meta: { rank: number } }) => { + return a?.meta.rank - b?.meta.rank; + } + ); +} + +/** 过滤meta中showLink为false的菜单 */ +function filterTree(data: RouteComponent[]) { + const newTree = cloneDeep(data).filter( + (v: { meta: { showLink: boolean } }) => v.meta?.showLink !== false + ); + newTree.forEach( + (v: { children }) => v.children && (v.children = filterTree(v.children)) + ); + return newTree; +} + +/** 过滤children长度为0的的目录,当目录下没有菜单时,会过滤此目录,目录没有赋予roles权限,当目录下只要有一个菜单有显示权限,那么此目录就会显示 */ +function filterChildrenTree(data: RouteComponent[]) { + const newTree = cloneDeep(data).filter((v: any) => v?.children?.length !== 0); + newTree.forEach( + (v: { children }) => v.children && (v.children = filterTree(v.children)) + ); + return newTree; +} + +/** 判断两个数组彼此是否存在相同值 */ +function isOneOfArray(a: Array, b: Array) { + return Array.isArray(a) && Array.isArray(b) + ? intersection(a, b).length > 0 + ? true + : false + : true; +} + +/** 从sessionStorage里取出当前登陆用户的角色roles,过滤无权限的菜单 */ +function filterNoPermissionTree(data: RouteComponent[]) { + const currentRoles = + storageSession().getItem>(sessionKey)?.roles ?? []; + const newTree = cloneDeep(data).filter((v: any) => + isOneOfArray(v.meta?.roles, currentRoles) + ); + newTree.forEach( + (v: any) => v.children && (v.children = filterNoPermissionTree(v.children)) + ); + return filterChildrenTree(newTree); +} + +/** 通过指定 `key` 获取父级路径集合,默认 `key` 为 `path` */ +function getParentPaths(value: string, routes: RouteRecordRaw[], key = "path") { + // 深度遍历查找 + function dfs(routes: RouteRecordRaw[], value: string, parents: string[]) { + for (let i = 0; i < routes.length; i++) { + const item = routes[i]; + // 返回父级path + if (item[key] === value) return parents; + // children不存在或为空则不递归 + if (!item.children || !item.children.length) continue; + // 往下查找时将当前path入栈 + parents.push(item.path); + + if (dfs(item.children, value, parents).length) return parents; + // 深度遍历查找未找到时当前path 出栈 + parents.pop(); + } + // 未找到时返回空数组 + return []; + } + + return dfs(routes, value, []); +} + +/** 查找对应 `path` 的路由信息 */ +function findRouteByPath(path: string, routes: RouteRecordRaw[]) { + let res = routes.find((item: { path: string }) => item.path == path); + if (res) { + return isProxy(res) ? toRaw(res) : res; + } else { + for (let i = 0; i < routes.length; i++) { + if ( + routes[i].children instanceof Array && + routes[i].children.length > 0 + ) { + res = findRouteByPath(path, routes[i].children); + if (res) { + return isProxy(res) ? toRaw(res) : res; + } + } + } + return null; + } +} + +function addPathMatch() { + if (!router.hasRoute("pathMatch")) { + router.addRoute({ + path: "/:pathMatch(.*)", + name: "pathMatch", + redirect: "/error/404" + }); + } +} + +/** 处理动态路由(后端返回的路由) */ +function handleAsyncRoutes(routeList) { + if (routeList.length === 0) { + usePermissionStoreHook().handleWholeMenus(routeList); + } else { + formatFlatteningRoutes(addAsyncRoutes(routeList)).map( + (v: RouteRecordRaw) => { + // 防止重复添加路由 + if ( + router.options.routes[0].children.findIndex( + value => value.path === v.path + ) !== -1 + ) { + return; + } else { + // 切记将路由push到routes后还需要使用addRoute,这样路由才能正常跳转 + router.options.routes[0].children.push(v); + // 最终路由进行升序 + ascending(router.options.routes[0].children); + if (!router.hasRoute(v?.name)) router.addRoute(v); + const flattenRouters: any = router + .getRoutes() + .find(n => n.path === "/"); + router.addRoute(flattenRouters); + } + } + ); + usePermissionStoreHook().handleWholeMenus(routeList); + } + addPathMatch(); +} + +/** 初始化路由(`new Promise` 写法防止在异步请求中造成无限循环)*/ +function initRouter() { + if (getConfig()?.CachingAsyncRoutes) { + // 开启动态路由缓存本地sessionStorage + const key = "async-routes"; + const asyncRouteList = storageSession().getItem(key) as any; + if (asyncRouteList && asyncRouteList?.length > 0) { + return new Promise(resolve => { + handleAsyncRoutes(asyncRouteList); + resolve(router); + }); + } else { + return new Promise(resolve => { + getAsyncRoutes().then(({ data }) => { + handleAsyncRoutes(cloneDeep(data)); + storageSession().setItem(key, data); + resolve(router); + }); + }); + } + } else { + return new Promise(resolve => { + getAsyncRoutes().then(({ data }) => { + handleAsyncRoutes(cloneDeep(data)); + resolve(router); + }); + }); + } +} + +/** + * 将多级嵌套路由处理成一维数组 + * @param routesList 传入路由 + * @returns 返回处理后的一维路由 + */ +function formatFlatteningRoutes(routesList: RouteRecordRaw[]) { + if (routesList.length === 0) return routesList; + let hierarchyList = buildHierarchyTree(routesList); + for (let i = 0; i < hierarchyList.length; i++) { + if (hierarchyList[i].children) { + hierarchyList = hierarchyList + .slice(0, i + 1) + .concat(hierarchyList[i].children, hierarchyList.slice(i + 1)); + } + } + return hierarchyList; +} + +/** + * 一维数组处理成多级嵌套数组(三级及以上的路由全部拍成二级,keep-alive 只支持到二级缓存) + * https://github.com/pure-admin/vue-pure-admin/issues/67 + * @param routesList 处理后的一维路由菜单数组 + * @returns 返回将一维数组重新处理成规定路由的格式 + */ +function formatTwoStageRoutes(routesList: RouteRecordRaw[]) { + if (routesList.length === 0) return routesList; + const newRoutesList: RouteRecordRaw[] = []; + routesList.forEach((v: RouteRecordRaw) => { + if (v.path === "/") { + newRoutesList.push({ + component: v.component, + name: v.name, + path: v.path, + redirect: v.redirect, + meta: v.meta, + children: [] + }); + } else { + newRoutesList[0]?.children.push({ ...v }); + } + }); + return newRoutesList; +} + +/** 处理缓存路由(添加、删除、刷新) */ +function handleAliveRoute({ name }: ToRouteType, mode?: string) { + switch (mode) { + case "add": + usePermissionStoreHook().cacheOperate({ + mode: "add", + name + }); + break; + case "delete": + usePermissionStoreHook().cacheOperate({ + mode: "delete", + name + }); + break; + case "refresh": + usePermissionStoreHook().cacheOperate({ + mode: "refresh", + name + }); + break; + default: + usePermissionStoreHook().cacheOperate({ + mode: "delete", + name + }); + useTimeoutFn(() => { + usePermissionStoreHook().cacheOperate({ + mode: "add", + name + }); + }, 100); + } +} + +/** 过滤后端传来的动态路由 重新生成规范路由 */ +function addAsyncRoutes(arrRoutes: Array) { + if (!arrRoutes || !arrRoutes.length) return; + const modulesRoutesKeys = Object.keys(modulesRoutes); + arrRoutes.forEach((v: RouteRecordRaw) => { + // 将backstage属性加入meta,标识此路由为后端返回路由 + v.meta.backstage = true; + // 父级的redirect属性取值:如果子级存在且父级的redirect属性不存在,默认取第一个子级的path;如果子级存在且父级的redirect属性存在,取存在的redirect属性,会覆盖默认值 + if (v?.children && v.children.length && !v.redirect) + v.redirect = v.children[0].path; + // 父级的name属性取值:如果子级存在且父级的name属性不存在,默认取第一个子级的name;如果子级存在且父级的name属性存在,取存在的name属性,会覆盖默认值(注意:测试中发现父级的name不能和子级name重复,如果重复会造成重定向无效(跳转404),所以这里给父级的name起名的时候后面会自动加上`Parent`,避免重复) + if (v?.children && v.children.length && !v.name) + v.name = (v.children[0].name as string) + "Parent"; + if (v.meta?.frameSrc) { + v.component = IFrame; + } else { + // 对后端传component组件路径和不传做兼容(如果后端传component组件路径,那么path可以随便写,如果不传,component组件路径会跟path保持一致) + const index = v?.component + ? modulesRoutesKeys.findIndex(ev => ev.includes(v.component as any)) + : modulesRoutesKeys.findIndex(ev => ev.includes(v.path)); + v.component = modulesRoutes[modulesRoutesKeys[index]]; + } + if (v?.children && v.children.length) { + addAsyncRoutes(v.children); + } + }); + return arrRoutes; +} + +/** 获取路由历史模式 https://next.router.vuejs.org/zh/guide/essentials/history-mode.html */ +function getHistoryMode(routerHistory): RouterHistory { + // len为1 代表只有历史模式 为2 代表历史模式中存在base参数 https://next.router.vuejs.org/zh/api/#%E5%8F%82%E6%95%B0-1 + const historyMode = routerHistory.split(","); + const leftMode = historyMode[0]; + const rightMode = historyMode[1]; + // no param + if (historyMode.length === 1) { + if (leftMode === "hash") { + return createWebHashHistory(""); + } else if (leftMode === "h5") { + return createWebHistory(""); + } + } //has param + else if (historyMode.length === 2) { + if (leftMode === "hash") { + return createWebHashHistory(rightMode); + } else if (leftMode === "h5") { + return createWebHistory(rightMode); + } + } +} + +/** 获取当前页面按钮级别的权限 */ +function getAuths(): Array { + return router.currentRoute.value.meta.auths as Array; +} + +/** 是否有按钮级别的权限 */ +function hasAuth(value: string | Array): boolean { + if (!value) return false; + /** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */ + const metaAuths = getAuths(); + if (!metaAuths) return false; + const isAuths = isString(value) + ? metaAuths.includes(value) + : isIncludeAllChildren(value, metaAuths); + return isAuths ? true : false; +} + +/** 获取所有菜单中的第一个菜单(顶级菜单)*/ +function getTopMenu(tag = false): menuType { + const topMenu = usePermissionStoreHook().wholeMenus[0]?.children[0]; + tag && useMultiTagsStoreHook().handleTags("push", topMenu); + return topMenu; +} + +export { + hasAuth, + getAuths, + ascending, + filterTree, + initRouter, + getTopMenu, + addPathMatch, + isOneOfArray, + getHistoryMode, + addAsyncRoutes, + getParentPaths, + findRouteByPath, + handleAliveRoute, + formatTwoStageRoutes, + formatFlatteningRoutes, + filterNoPermissionTree +}; diff --git a/src/pages/dataScreen/views/computePowerAllocation/components/computePowerCube.vue b/src/pages/dataScreen/views/computePowerAllocation/components/computePowerCube.vue new file mode 100644 index 0000000..ef7d867 --- /dev/null +++ b/src/pages/dataScreen/views/computePowerAllocation/components/computePowerCube.vue @@ -0,0 +1,205 @@ + + + + + + diff --git a/src/pages/dataScreen/views/computePowerAllocation/components/computePowerType.vue b/src/pages/dataScreen/views/computePowerAllocation/components/computePowerType.vue new file mode 100644 index 0000000..db635e1 --- /dev/null +++ b/src/pages/dataScreen/views/computePowerAllocation/components/computePowerType.vue @@ -0,0 +1,104 @@ + + + + + + diff --git a/src/pages/dataScreen/views/computePowerAllocation/computePowerAllocation.scss b/src/pages/dataScreen/views/computePowerAllocation/computePowerAllocation.scss new file mode 100644 index 0000000..95644df --- /dev/null +++ b/src/pages/dataScreen/views/computePowerAllocation/computePowerAllocation.scss @@ -0,0 +1,81 @@ +.computePowerAllocation_wrap { + height: 100%; + + // padding-top: calc(50vh - 330px - 48px); + .computePowerAllocation_body { + border-radius: 12px; + background: rgba(21, 77, 221, 0.05); + width: 1080px; + height: 660px; + margin: 0 auto; + } + + .computePower_header { + text-align: center; + position: relative; + height: 46px; + + & > span { + position: absolute; + line-height: 46px; + font-size: 24px; + font-weight: 700; + background: linear-gradient(180deg, #014be6 0%, #014be6 100%); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + } + } + + .computePower_banner { + padding: 32px 32px 0; + + .banner_left { + .banner_group { + & > li { + width: 266px; + height: 88px; + border-radius: 8px; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.1); + position: relative; + overflow: hidden; + + .bg_banner_group_logo { + width: 266px; + height: 166.25px; + top: -38px; + position: absolute; + left: 52px; + background: url("@/assets/computePower/computerTypeLogo.png") + no-repeat 0 0; + background-size: cover; + opacity: 0.05; + } + } + } + } + + .bg_banner_center { + width: 360px; + height: 360px; + background: url("@/assets/computePower/banner.png"); + background-repeat: no-repeat; + background-size: contain; + } + + .banner_right { + .computePowerCube_wrap { + padding: 16px; + width: 266px; + height: 400px; + border-radius: 8px; + background-color: white; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.1); + } + } + } + + .computePower_footer { + padding: 10px; + } +} diff --git a/src/pages/dataScreen/views/computePowerAllocation/index.vue b/src/pages/dataScreen/views/computePowerAllocation/index.vue new file mode 100644 index 0000000..0c52fc4 --- /dev/null +++ b/src/pages/dataScreen/views/computePowerAllocation/index.vue @@ -0,0 +1,146 @@ + + + + + + diff --git a/src/pages/dataScreen/views/computePowerAllocation/typing.ts b/src/pages/dataScreen/views/computePowerAllocation/typing.ts new file mode 100644 index 0000000..93925d6 --- /dev/null +++ b/src/pages/dataScreen/views/computePowerAllocation/typing.ts @@ -0,0 +1,16 @@ +/* + * @Author: donghao donghao@supervision.ltd + * @Date: 2024-01-22 13:30:43 + * @LastEditors: donghao donghao@supervision.ltd + * @LastEditTime: 2024-01-22 13:30:53 + * @FilePath: \General-AI-Platform-Web-Client\src\views\computePowerAllocation\typing.ts + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ + +export type ComputePowerPoolItem = { + name: string; + type: number; // 类别 + color?: string; // + proportion: number; + pretreatmentEfficiency?: number; +}; diff --git a/src/pages/dataScreen/views/home/homeIndex.scss b/src/pages/dataScreen/views/home/homeIndex.scss new file mode 100644 index 0000000..3adcf42 --- /dev/null +++ b/src/pages/dataScreen/views/home/homeIndex.scss @@ -0,0 +1,5 @@ +.left_box_1 { + width: 430px; + height: 288px; + background: url("@/assets/svg/screenBgCommon.svg") no-repeat; +} diff --git a/src/pages/dataScreen/views/home/homeIndex.vue b/src/pages/dataScreen/views/home/homeIndex.vue new file mode 100644 index 0000000..6dac01e --- /dev/null +++ b/src/pages/dataScreen/views/home/homeIndex.vue @@ -0,0 +1,26 @@ + + + + diff --git a/vite.config.ts b/vite.config.ts index 8a7a336..4422085 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,3 +1,11 @@ +/* + * @Author: donghao donghao@supervision.ltd + * @Date: 2024-02-22 13:38:05 + * @LastEditors: donghao donghao@supervision.ltd + * @LastEditTime: 2024-02-22 14:18:31 + * @FilePath: \General-AI-Platform-Web-Client\vite.config.ts + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ import dayjs from "dayjs"; import { resolve } from "path"; import pkg from "./package.json"; @@ -57,7 +65,8 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => { chunkSizeWarningLimit: 4000, rollupOptions: { input: { - index: pathResolve("index.html") + index: pathResolve("index.html"), + indexDataScreen: pathResolve("indexDataScreen.html") }, // 静态资源分类打包 output: {