- A 类主题 loader 原文接入 + env-config 引导 + vite /static 与 /api(8086) 代理(P6/P11)
- GetUserMod 左导航:utils/menuNav.ts 单点实现 path 空锚点跳过、分组收敛、icon 兜底、面包屑基线(对齐 Notice 13afb22 refined 版)
- alova 封装 + api/module/base.ts(getUserMod 可选 appCode,P2 定名;H4 前空树不回落,P3)
- 路由骨架一 SPA 四形态:/admin/{forms,data,stats,dicts}、/f/:code、/app/:appCode/*、/my;self!=top 剥壳(方案 §3.3)
- 规范件落位:tableActions.ts(UserBase 同构源,正文 MD5 不变)、TablePageAlovaNew + useElementHeight、page-wrapper yh-fill 骨架
- view-ui-plus 精确锁 1.3.24;本地零 layout.css/icons.css;avatar 两件由 YschTheme sync 下发
- 剥除:notice 领域 views/api/mock/menu 兜底、alova-vant(mobiapi)、ztree+jquery 与重依赖(按需回补,见 service/docs/T5-report.md 问题单 P52)
This commit is contained in:
+130
-10
@@ -1,15 +1,135 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, reactive, ref } from "vue";
|
||||
import layout from "./components/layout.vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import config from "@/assets/config/host.config";
|
||||
import { baseApi } from "@/api/module/base";
|
||||
import { resolveMenuPage } from "@/utils/menuRoute";
|
||||
import { buildNavTree, firstNavPage, isSingleNav, type NavNode } from "@/utils/menuNav";
|
||||
import { Message } from "view-ui-plus";
|
||||
import type { IModule } from "@/types/Basics";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const isReady = ref(false);
|
||||
|
||||
/**
|
||||
* 页面即组件(方案 §3.3):被 iframe 嵌入时自动剥壳——不渲染顶栏/左导航/面包屑,
|
||||
* 也不发两个鉴权请求(嵌入上下文里本应用 /api/base/* 取不到会话会弹错误提示并画出带壳页面)。
|
||||
* 显式 mode 参数(?mode=embed|page)的闭集判定在 T7 填报页落地,壳这层只兜 self != top 这一条。
|
||||
*/
|
||||
const isEmbedded = window.self !== window.top;
|
||||
|
||||
/**
|
||||
* 嵌入态必须旁路全局 body 的 min-width(assets/styles/base/_base.scss 的 1200px):
|
||||
* isEmbedded 只旁路了组件壳,管不到全局 CSS,窄 iframe 里仍按 ≥1200px 排版(整体右偏 + 右裁)。
|
||||
* 内联样式优先级高于样式表;非嵌入态移除内联属性回落原值。
|
||||
*/
|
||||
function syncEmbedLayout(embedded: boolean) {
|
||||
if (embedded) document.body.style.minWidth = "0";
|
||||
else document.body.style.removeProperty("min-width");
|
||||
}
|
||||
|
||||
const layoutData = reactive({
|
||||
appName: "表单中心",
|
||||
baseHost: config.baseHost,
|
||||
ssoHost: config.ssoHost,
|
||||
userInfo: null as any,
|
||||
/** 已按「path 空=权限锚点不进导航」规则过滤的可渲染菜单树(见 utils/menuNav.ts) */
|
||||
navList: [] as NavNode[],
|
||||
isAlone: false,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
// 首次导航异步完成后再判默认跳转:mount 时 currentRoute 可能仍是初始 "/"
|
||||
await router.isReady();
|
||||
syncEmbedLayout(isEmbedded);
|
||||
isReady.value = true;
|
||||
if (isEmbedded) return;
|
||||
|
||||
try {
|
||||
// 1. 当前会话用户
|
||||
const user: any = await baseApi.getUserInfo().send();
|
||||
const { id, name, profilePicture } = user || {};
|
||||
|
||||
// 2. 当前用户已授权的模块树(左导航数据源;path 空的权限锚点行也在内,由导航层跳过)
|
||||
const modList: IModule[] = (await baseApi.getUserMod().send()) || [];
|
||||
layoutData.userInfo = { id, name: name || "用户", avatar: profilePicture || "" };
|
||||
layoutData.navList = buildNavTree(modList, resolveMenuPage);
|
||||
layoutData.isAlone = isSingleNav(layoutData.navList);
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// 冒烟取证:原始树 vs 可渲染导航(path 空的权限锚点应当只出现在前者)
|
||||
console.table(
|
||||
(modList || []).map((m: any) => ({
|
||||
name: m?.name,
|
||||
id: m?.id,
|
||||
modId: m?.modId,
|
||||
page: m?.page,
|
||||
icon: m?.icon,
|
||||
children: (m?.children || []).length
|
||||
}))
|
||||
);
|
||||
console.table(
|
||||
layoutData.navList.map((n) => ({ 渲染项: n.name, page: n.page, 分组头: n.group }))
|
||||
);
|
||||
}
|
||||
|
||||
// 入口页:跳第一个可跳转菜单项(无授权用户即「我的填写」)
|
||||
if (router.currentRoute.value.path === "/") {
|
||||
const first = firstNavPage(layoutData.navList);
|
||||
if (first) router.replace(first);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("加载用户信息失败", error);
|
||||
if (error?.message !== "redirect") {
|
||||
Message.error("载入用户信息失败,请检查后端服务是否正常!");
|
||||
}
|
||||
// 降级只降「导航为空」,不造兜底菜单树——静默回落会把「平台未配模块/未授权」伪装成正常
|
||||
layoutData.userInfo = { name: "用户", avatar: "" };
|
||||
layoutData.navList = [];
|
||||
layoutData.isAlone = true;
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => syncEmbedLayout(false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="shell-placeholder">
|
||||
<h1>表单中心</h1>
|
||||
<p>工程壳已就绪(T1)。管理端框架、设计器与填报页在 T5~T8 落地。</p>
|
||||
</main>
|
||||
<!-- 嵌入态:裸渲染,页面自己管理样式与滚动(方案 §3.3 剥壳) -->
|
||||
<router-view v-if="isEmbedded" :key="route.fullPath" />
|
||||
<template v-else>
|
||||
<layout v-if="isReady" :layoutData="layoutData" />
|
||||
<div v-if="isReady" class="base-wrap-main" :class="{ 'alone-page': layoutData.isAlone }">
|
||||
<div class="main-wrapper">
|
||||
<!-- key=fullPath:仅 query 变化的深链(如详情页换 id)强制重渲染,避免显示上一条数据 -->
|
||||
<router-view :key="route.fullPath" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped>
|
||||
.shell-placeholder {
|
||||
font-family: system-ui, -apple-system, "Microsoft YaHei", sans-serif;
|
||||
padding: 24px;
|
||||
<style lang="scss">
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
/* 页面级滚动容器:main-wrapper 定高 + 纵向滚动。内容超一屏时若只给 min-height,
|
||||
会被 body 的 overflow:hidden 直接裁掉且无滚动条;列表页高度由 .table-region 实测(规范 §4.3) */
|
||||
.base-wrap-main .main-wrapper {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
/* 表格操作列(operatingHandle/actionIcon 渲染的 Tooltip 包裹层)横排且随单元格居中 */
|
||||
.ivu-table-cell > .ivu-tooltip {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 0 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user