diff --git a/src/layouts/components/LayoutMain.vue b/src/layouts/components/LayoutMain.vue index 15273aa3..d6054b29 100644 --- a/src/layouts/components/LayoutMain.vue +++ b/src/layouts/components/LayoutMain.vue @@ -20,7 +20,7 @@ import { type RouteLocationNormalized } from "vue-router"; import { useSettingsStore, useTagsViewStore } from "@/stores"; import variables from "@/styles/variables.module.scss"; -import Error404 from "@/views/error/404.vue"; +import Error404 from "@/views/internal/error/404.vue"; const { cachedViews } = toRefs(useTagsViewStore()); diff --git a/src/router/index.ts b/src/router/index.ts index 32e7031d..7298a231 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,5 +1,6 @@ import type { App } from "vue"; import { createRouter, createWebHashHistory, type RouteRecordRaw } from "vue-router"; +import { ensureFormCreate } from "@/plugins/form-create"; /** * 布局组件,供各路由复用(懒加载) @@ -13,7 +14,7 @@ export const screenRoutes: RouteRecordRaw[] = [ { path: "/data-screen", name: "DataScreen", - component: () => import("@/views/data-screen/index.vue"), + component: () => import("@/views/demo/data-screen/index.vue"), meta: { hidden: true, title: "数据大屏" }, }, ]; @@ -27,7 +28,7 @@ export const constantRoutes: RouteRecordRaw[] = [ children: [ { path: "/redirect/:path(.*)", - component: () => import("@/views/redirect.vue"), + component: () => import("@/views/internal/redirect.vue"), }, ], }, @@ -38,11 +39,22 @@ export const constantRoutes: RouteRecordRaw[] = [ meta: { hidden: true }, }, + // 统一认证回调页(匿名访问,守卫白名单,只用票据换取业务令牌) + { + path: "/oidc/callback", + name: "OidcCallback", + component: () => import("@/views/login/oidc-callback.vue"), + meta: { hidden: true }, + }, + // 公开表单分享页(匿名访问,守卫白名单,不套管理端 Layout) { path: "/f/:formKey", name: "FormShare", - component: () => import("@/views/dynamic-form/share.vue"), + component: async () => { + await ensureFormCreate(); + return import("@/views/dynamic-form/share.vue"); + }, meta: { hidden: true, title: "表单填写" }, }, @@ -70,24 +82,24 @@ export const constantRoutes: RouteRecordRaw[] = [ }, { path: "401", - component: () => import("@/views/error/401.vue"), + component: () => import("@/views/internal/error/401.vue"), meta: { hidden: true }, }, { path: "404", - component: () => import("@/views/error/404.vue"), + component: () => import("@/views/internal/error/404.vue"), meta: { hidden: true }, }, { path: "profile", name: "Profile", - component: () => import("@/views/profile/index.vue"), + component: () => import("@/views/workspace/account/index.vue"), meta: { title: "个人中心", icon: "user", hidden: true }, }, { path: "profile/notice", name: "MyNotice", - component: () => import("@/views/profile/notice/index.vue"), + component: () => import("@/views/workspace/notifications/index.vue"), meta: { title: "我的通知", icon: "user", hidden: true }, }, { diff --git a/src/router/views.ts b/src/router/views.ts index 490fb95e..66a76302 100644 --- a/src/router/views.ts +++ b/src/router/views.ts @@ -2,6 +2,8 @@ // 维护「菜单 component 字符串 → src/views 视图文件」的映射,路由注册与菜单表单共用 // 表单的「页面路径」选项树也由此派生:跳过错误页与页面内部组件,index.vue 归并到目录 +import { ensureFormCreate } from "@/plugins/form-create"; + /** * 全部视图模块,键形如 /src/views/system/user/index.vue */ @@ -26,16 +28,36 @@ export function normalizeViewPath(componentPath: string): string { .replace(/\.vue$/i, ""); } +// 内嵌外链菜单的组件标识固定为 iframe,视图文件在 internal 下 +const VIEW_ALIASES: Record = { iframe: "internal/iframe" }; + +/** + * 把菜单里保存的组件标识映射到实际视图路径 + */ +function resolveViewAlias(path: string): string { + return VIEW_ALIASES[path] ?? path; +} + +// 用到 form-create 的视图:动态表单模块本身,以及内嵌表单的业务流程页 +const FORM_VIEW_PATTERN = /^(dynamic-form|workflow)(\/|$)/; + /** * 解析组件:支持 system/user 与 system/user/index 两种写法,未命中回退 404 */ export function resolveComponent(componentPath: string) { - const path = normalizeViewPath(componentPath); - return ( + const path = resolveViewAlias(normalizeViewPath(componentPath)); + const loader = viewModules[`${VIEW_PREFIX}${path}.vue`] || viewModules[`${VIEW_PREFIX}${path}/index.vue`] || - viewModules[`${VIEW_PREFIX}error/404.vue`] - ); + viewModules[`${VIEW_PREFIX}internal/error/404.vue`]; + + if (!FORM_VIEW_PATTERN.test(path)) return loader; + + // 表单页的 依赖全局注册,组件加载前先完成注册 + return async () => { + await ensureFormCreate(); + return loader(); + }; } /** @@ -43,7 +65,7 @@ export function resolveComponent(componentPath: string) { */ export function isViewComponent(componentPath?: string): boolean { if (!componentPath) return false; - const path = normalizeViewPath(componentPath); + const path = resolveViewAlias(normalizeViewPath(componentPath)); return viewPaths.has(path) || viewPaths.has(`${path}/index`); } @@ -64,7 +86,7 @@ export interface PageComponentNode { function isSelectable(path: string): boolean { const segments = path.split("/"); if (segments.includes("components")) return false; - return segments[0] !== "error" && path !== "redirect"; + return segments[0] !== "internal"; } /** diff --git a/src/views/dashboard/composables/useOnlineUsers.ts b/src/views/dashboard/composables/useOnlineUserCount.ts similarity index 64% rename from src/views/dashboard/composables/useOnlineUsers.ts rename to src/views/dashboard/composables/useOnlineUserCount.ts index 18dfce91..856f6b3b 100644 --- a/src/views/dashboard/composables/useOnlineUsers.ts +++ b/src/views/dashboard/composables/useOnlineUserCount.ts @@ -3,21 +3,15 @@ import { useSse } from "@/utils/sse"; import { SseTopics } from "@/enums/sse"; /** - * 在线用户数组合式函数(页面级) - * - * 订阅 SSE 在线用户数事件,组件挂载时订阅、卸载时自动取消; - * 进入页面后需等待下一次推送才有数据 + * 订阅在线用户数推送,首次推送前人数为 null */ -export function useOnlineUsers() { - const onlineUserCount = ref(0); +export function useOnlineUserCount() { + const onlineUserCount = ref(null); const { isConnected, on } = useSse(); let unsubscribe: (() => void) | null = null; - /** - * 处理 SSE 推来的在线人数消息 - */ const handleOnlineUsersMessage = (count: number) => { if (!Number.isFinite(count) || count < 0) return; onlineUserCount.value = count; @@ -30,7 +24,7 @@ export function useOnlineUsers() { onUnmounted(() => { unsubscribe?.(); unsubscribe = null; - onlineUserCount.value = 0; + onlineUserCount.value = null; }); return { diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue index 56312b0d..31927329 100644 --- a/src/views/dashboard/index.vue +++ b/src/views/dashboard/index.vue @@ -69,7 +69,7 @@
在线用户 - {{ onlineUserCount }} + {{ onlineUserCount ?? "--" }}
{ diff --git a/src/views/data-screen/utils/time.ts b/src/views/data-screen/utils/time.ts deleted file mode 100644 index e9119138..00000000 --- a/src/views/data-screen/utils/time.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * 时间格式化:按 pattern 里的占位符替换为对应时间片段 - * - * 支持占位符:YYYY/YY/MM/M/DD/D/HH/H/hh/h/mm/m/ss/s/SSS(A 为上午下午,dd 为星期,d 为星期序号) - * - * @param time 时间对象、时间戳或时间字符串 - * @param pattern 输出格式,默认 "YYYY-MM-DD HH:mm:ss" - */ -export function parseTime(time: Date | string | number, pattern = "YYYY-MM-DD HH:mm:ss"): string { - let date: Date; - - if (time instanceof Date) { - date = time; - } else if (typeof time === "number") { - // 秒级时间戳补成毫秒 - date = time.toString().length < 13 ? new Date(time * 1000) : new Date(time); - } else { - date = new Date(time); - } - - const weekdays = ["日", "一", "二", "三", "四", "五", "六"]; - const year = date.getFullYear(); - const month = date.getMonth() + 1; - const day = date.getDate(); - const hours = date.getHours(); - const minutes = date.getMinutes(); - const seconds = date.getSeconds(); - const milliseconds = date.getMilliseconds(); - const hours12 = hours % 12 || 12; - - const formatMap: Record = { - YYYY: String(year), - YY: String(year).slice(-2), - MM: String(month).padStart(2, "0"), - M: String(month), - DD: String(day).padStart(2, "0"), - D: String(day), - HH: String(hours).padStart(2, "0"), - H: String(hours), - hh: String(hours12).padStart(2, "0"), - h: String(hours12), - mm: String(minutes).padStart(2, "0"), - m: String(minutes), - ss: String(seconds).padStart(2, "0"), - s: String(seconds), - SSS: String(milliseconds).padStart(3, "0"), - A: hours < 12 ? "上午" : "下午", - dd: weekdays[date.getDay()], - d: String(date.getDay()), - }; - - return pattern.replace( - /YYYY|YY|MM|M|DD|D|HH|H|hh|h|mm|m|ss|s|SSS|A|dd|d/g, - (match) => formatMap[match] || match - ); -} diff --git a/src/views/data-screen/assets/chinaGeoJSON.json b/src/views/demo/data-screen/assets/chinaGeoJSON.json similarity index 100% rename from src/views/data-screen/assets/chinaGeoJSON.json rename to src/views/demo/data-screen/assets/chinaGeoJSON.json diff --git a/src/views/data-screen/assets/fonts/DIN.woff b/src/views/demo/data-screen/assets/fonts/DIN.woff similarity index 100% rename from src/views/data-screen/assets/fonts/DIN.woff rename to src/views/demo/data-screen/assets/fonts/DIN.woff diff --git a/src/views/data-screen/assets/fonts/MetroDF.woff b/src/views/demo/data-screen/assets/fonts/MetroDF.woff similarity index 100% rename from src/views/data-screen/assets/fonts/MetroDF.woff rename to src/views/demo/data-screen/assets/fonts/MetroDF.woff diff --git a/src/views/data-screen/assets/fonts/YouSheBiaoTiHei.woff b/src/views/demo/data-screen/assets/fonts/YouSheBiaoTiHei.woff similarity index 100% rename from src/views/data-screen/assets/fonts/YouSheBiaoTiHei.woff rename to src/views/demo/data-screen/assets/fonts/YouSheBiaoTiHei.woff diff --git a/src/views/data-screen/assets/fonts/font.scss b/src/views/demo/data-screen/assets/fonts/font.scss similarity index 100% rename from src/views/data-screen/assets/fonts/font.scss rename to src/views/demo/data-screen/assets/fonts/font.scss diff --git a/src/views/data-screen/assets/images/bg.webp b/src/views/demo/data-screen/assets/images/bg.webp similarity index 100% rename from src/views/data-screen/assets/images/bg.webp rename to src/views/demo/data-screen/assets/images/bg.webp diff --git a/src/views/data-screen/assets/images/header-bg.webp b/src/views/demo/data-screen/assets/images/header-bg.webp similarity index 100% rename from src/views/data-screen/assets/images/header-bg.webp rename to src/views/demo/data-screen/assets/images/header-bg.webp diff --git a/src/views/data-screen/assets/images/line.webp b/src/views/demo/data-screen/assets/images/line.webp similarity index 100% rename from src/views/data-screen/assets/images/line.webp rename to src/views/demo/data-screen/assets/images/line.webp diff --git a/src/views/data-screen/assets/images/ray.webp b/src/views/demo/data-screen/assets/images/ray.webp similarity index 100% rename from src/views/data-screen/assets/images/ray.webp rename to src/views/demo/data-screen/assets/images/ray.webp diff --git a/src/views/data-screen/assets/styles/index.scss b/src/views/demo/data-screen/assets/styles/index.scss similarity index 100% rename from src/views/data-screen/assets/styles/index.scss rename to src/views/demo/data-screen/assets/styles/index.scss diff --git a/src/views/data-screen/components/base-chart.vue b/src/views/demo/data-screen/components/base-chart.vue similarity index 100% rename from src/views/data-screen/components/base-chart.vue rename to src/views/demo/data-screen/components/base-chart.vue diff --git a/src/views/data-screen/components/battery-health.vue b/src/views/demo/data-screen/components/battery-health.vue similarity index 100% rename from src/views/data-screen/components/battery-health.vue rename to src/views/demo/data-screen/components/battery-health.vue diff --git a/src/views/data-screen/components/center-charts.vue b/src/views/demo/data-screen/components/center-charts.vue similarity index 100% rename from src/views/data-screen/components/center-charts.vue rename to src/views/demo/data-screen/components/center-charts.vue diff --git a/src/views/data-screen/components/cephalosome.vue b/src/views/demo/data-screen/components/cephalosome.vue similarity index 60% rename from src/views/data-screen/components/cephalosome.vue rename to src/views/demo/data-screen/components/cephalosome.vue index f1ad393c..7d8ab32e 100644 --- a/src/views/data-screen/components/cephalosome.vue +++ b/src/views/demo/data-screen/components/cephalosome.vue @@ -21,14 +21,25 @@ + + diff --git a/src/views/profile/index.vue b/src/views/workspace/account/index.vue similarity index 100% rename from src/views/profile/index.vue rename to src/views/workspace/account/index.vue diff --git a/src/views/workspace/apps/index.vue b/src/views/workspace/apps/index.vue new file mode 100644 index 00000000..48afcbc2 --- /dev/null +++ b/src/views/workspace/apps/index.vue @@ -0,0 +1,390 @@ + + + + + + diff --git a/src/views/profile/notice/index.vue b/src/views/workspace/notifications/index.vue similarity index 100% rename from src/views/profile/notice/index.vue rename to src/views/workspace/notifications/index.vue