设置界面更新
build / build (push) Successful in 1m8s

This commit is contained in:
chenxing
2026-10-08 10:08:22 +08:00
parent e2c937f424
commit 19e1bbc9d7
182 changed files with 11133 additions and 0 deletions
+260
View File
@@ -0,0 +1,260 @@
/**
* 视觉取证 harness 入口(步骤 4 审计专用,不进 git)。
* 复刻 App.vue 壳层(.app-desktop + base-mask + LeftMenu + Header + 真实路由页),
* 用 store.$patch mock 三档登录态(?role=super|admin|user)驱动真实路由页渲染;
* 本机无后端,接口失败均静默/走失败态展示(/msg 页取证失败态正是验收点之一)。
* 页内断言器(#assert-out):留白四边 15px、msg-side 216px、主题 iframe 内外 data-theme/通道 link 分布。
* $conf.baseHost 指向本地通道镜像(YschTheme serve:static 9527)——仅预览 iframe 的 init 走它。
*/
import { createApp, defineComponent, h } from "vue";
import { createPinia } from "pinia";
import { createWebHashHistory, createRouter, RouterView } from "vue-router";
import ViewUIPlus from "view-ui-plus";
import "view-ui-plus/dist/styles/viewuiplus.css";
import "@/assets/styles/main.scss";
import vuescroll from "vuescroll";
import VWave from "v-wave";
import mitt from "mitt";
import routes from "@/router/routes";
// 懒加载 chunk 在 headless virtual-time 下不可靠(fetch 挂起 → router-view 恒空注释),
// 取证路由改静态 import 同构复刻(路径/name/meta 与 routes.ts 保持一致)
import MsgPage from "@/views/msg/index.vue";
import SettingsPage from "@/views/Settings.vue";
import ThemePreviewPage from "@/views/ThemePreview.vue";
// 默认头像轮取证新增:AppHeader(顶栏三态头像)+ UserInfoModal(120px 弹窗头像)
import AppHeader from "@/components/AppHeader.vue";
import UserInfoModal from "@/components/UserInfoModal.vue";
import mittBus from "@/utils/mitt";
const harnessRoutes: any[] = [
{ path: "/msg", name: "Msg", component: MsgPage, meta: { title: "消息" } },
{ path: "/settings", name: "Settings", component: SettingsPage, meta: { title: "设置" } },
{ path: "/theme-preview", name: "ThemePreview", component: ThemePreviewPage, meta: { title: "主题预览" } },
{ path: "/appheader", name: "AppHeaderDemo", component: null as any, meta: { title: "顶栏头像取证" } },
{ path: "/", name: "index", component: MsgPage, meta: { title: "我的桌面" } },
];
import { useDefaultStore } from "@/store";
import LeftMenu from "@/components/LeftMenu.vue";
import MyHeader from "@/components/Header.vue";
import ThemePreview from "@/views/ThemePreview.vue";
import directive from "@/utils/directive/index";
import utils from "@/utils";
import "virtual:svg-icons-register";
const params = new URLSearchParams(location.search);
const role = params.get("role") || "admin";
/** mock 三档登录态(照真实字段名):super 超管 type=1;admin 校管 roleId=2;user 普通教师 roleId=3。
* ?avatar=img 追加本地 data-URI 头像(默认头像轮三态取证:有图 / ?yh-avatar=text 首字 / ?yh-avatar=silhouette 剪影) */
function applyMockState() {
const store = useDefaultStore();
const profiles: Record<string, any> = {
super: { userInfo: { id: 1, type: 1, name: "平台管理员", account: "admin", phone: "13800000001" }, roleId: 1 },
admin: { userInfo: { id: 2, type: 0, name: "王老师", account: "T20240315", phone: "13800000002" }, roleId: 2 },
user: { userInfo: { id: 3, type: 0, name: "李老师", account: "T20240316", phone: "13800000003" }, roleId: 3 },
};
const p = profiles[role] || profiles.admin;
const userInfo = { ...p.userInfo };
if (params.get("avatar") === "img") {
userInfo.profilePicture =
"data:image/svg+xml;utf8," +
encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f6b352"/><stop offset="1" stop-color="#f68d52"/></linearGradient></defs><rect width="120" height="120" rx="60" fill="url(#g)"/><circle cx="60" cy="46" r="20" fill="#fff"/><path d="M22 104a38 38 0 0 1 76 0z" fill="#fff"/></svg>'
);
}
store.$patch({
userInfo,
curRoleInfo: { roleId: p.roleId, roleName: "", schId: 1, schName: "云海一中", childId: 0, childName: "" },
userRoleList: [],
curUserMenuList: [
{ id: 1, name: "消息", url: "/msg", isNative: true, isBottom: true, orderNum: 9998, icon: "" },
{ id: 2, name: "设置", url: "/settings", isNative: true, isBottom: true, orderNum: 9999, icon: "" },
],
curUserAppList: [
{
apps: [
{ appKey: "100019", appCode: "flow", name: "流程中心" },
{ appKey: "100617", appCode: "notice", name: "通知公告" },
{ appKey: "100611", appCode: "leave", name: "请假" },
],
},
],
unreadCount: 5,
curUserSettingInfo: {
wallpaperUrl:
"data:image/svg+xml;utf8," +
encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="900"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#dce7f2"/><stop offset="1" stop-color="#cfd9e6"/></linearGradient></defs><rect width="1600" height="900" fill="url(#g)"/></svg>'
),
} as any,
});
return store;
}
const Shell = defineComponent({
setup() {
const store = applyMockState();
const wallpaper = store.curUserSettingInfo.wallpaperUrl;
return () =>
h("div", { class: "app-desktop app-desktop__animation", style: { backgroundImage: `url(${wallpaper})` } }, [
h(LeftMenu),
h("div", { class: "app-main-wrap base-mask" }, [
h(MyHeader),
h("div", { class: "harness-router" }, [h(RouterView)]),
]),
]);
},
});
// /appheader:顶栏三态头像取证页(AppHeader + UserInfoModal 同屏;挂载后自动弹个人信息弹窗,一屏证 28px/120px 两处消费)
const AvatarShell = defineComponent({
setup() {
applyMockState();
setTimeout(() => mittBus.emit("showUserInfoModal", true), 800);
return () =>
h("div", { style: "position:relative;min-height:100vh;background:#f5f6f8" }, [
h(AppHeader, { appName: "通知公告" }),
h(UserInfoModal),
]);
},
});
harnessRoutes.find(r => r.path === "/appheader").component = AvatarShell;
// /theme-preview 直开(不经设置页)取证:走 App.vue 同款顶层独立分支语义(不进壳层、不依赖登录态)
const PreviewShell = defineComponent({
setup() {
return () => h(ThemePreview);
},
});
const router = createRouter({
history: createWebHashHistory(),
routes: harnessRoutes,
});
import { useRoute } from "vue-router";
const app = createApp(
defineComponent({
// 顶层分支必须用 setup 内的 useRoute()(响应式):原先捕获 currentRoute.value(非响应式解包),
// 直开 /theme-preview 时首帧按初始路由 "/" 渲染了 Shell,预览被套进桌面壳里
setup() {
const rt = useRoute();
return () =>
h("div", [
rt.path === "/theme-preview" ? h(PreviewShell) : rt.path === "/appheader" ? h(AvatarShell) : h(Shell),
]);
},
})
);
app.use(createPinia());
app.use(router);
app.use(VWave as any);
app.use(ViewUIPlus);
app.use(vuescroll as any, { ops: { bar: { keepShow: true, background: "#595959" } } } as any);
app.config.globalProperties.$conf = {
baseHost: "http://localhost:9527",
ssoHost: "",
serviceHost: "",
fileHost: "",
ifameAppIdArr: [],
statsRealyConf: [],
isMock: true,
isDebug: true,
};
app.config.globalProperties.$utils = utils;
app.config.globalProperties.$bus = mitt();
directive(app);
app.mount("#app");
// ===== 诊断探针(取证调试用):全局错误与路由就绪状态写进 DOM =====
window.addEventListener("error", e => {
const d = document.createElement("div");
d.id = "js-error-" + (document.querySelectorAll("[id^=js-error]").length + 1);
d.style.cssText = "position:fixed;top:0;right:0;z-index:99999;background:#fdd;color:#900;font-size:12px;padding:4px 8px;max-width:60%;";
d.textContent = "JSERR:" + (e.message || String(e));
document.body.appendChild(d);
});
window.addEventListener("unhandledrejection", e => {
const d = document.createElement("div");
d.id = "js-reject-" + (document.querySelectorAll("[id^=js-reject]").length + 1);
d.style.cssText = "position:fixed;top:0;right:0;z-index:99999;background:#fed;color:#960;font-size:12px;padding:4px 8px;max-width:60%;";
d.textContent = "REJ:" + String((e as any).reason || "").slice(0, 200);
document.body.appendChild(d);
});
router.isReady().then(
() => {
const d = document.createElement("div");
d.id = "router-ready";
d.style.cssText = "position:fixed;top:0;left:0;z-index:99999;background:#dfd;color:#060;font-size:12px;padding:2px 8px;";
d.textContent = "ROUTER-READY:" + router.currentRoute.value.fullPath;
document.body.appendChild(d);
},
err => {
const d = document.createElement("div");
d.id = "router-ready";
d.style.cssText = "position:fixed;top:0;left:0;z-index:99999;background:#fdd;color:#900;font-size:12px;padding:2px 8px;";
d.textContent = "ROUTER-FAIL:" + String(err).slice(0, 300);
document.body.appendChild(d);
}
);
// ===== 页内断言器(轮询等目标元素/iframe 就绪后输出,结果写 #assert-out,配 --dump-dom / 截图提取)=====
function runAssert(attempt: number) {
const gs = (el: Element | null, prop: string) => (el ? getComputedStyle(el)[prop as any] : "NOELEM");
const shell = document.querySelector(".set-shell") || document.querySelector(".msg-shell") || document.querySelector(".theme-preview-root");
const iframe = document.querySelector(".theme-iframe") as HTMLIFrameElement | null;
// 等待条件:目标壳层已渲染;若页面含预览 iframe,还要等 iframe 应用加载出 router 内容
let idoc: Document | null = null;
try {
idoc = iframe ? iframe.contentDocument : null;
} catch (e) {
/* 跨源(不应发生:同源) */
}
const iframeReady = !iframe || (idoc?.querySelector(".theme-preview-root") && idoc.querySelectorAll("link[data-yh-theme-css], script[data-yh-theme-init]").length > 0);
if ((!shell || !iframeReady) && attempt < 120) {
setTimeout(() => runAssert(attempt + 1), 500);
return;
}
const out: string[] = [];
out.push("role=" + role);
out.push("wait-attempts=" + attempt);
out.push("set-shell.padding=" + gs(document.querySelector(".set-shell"), "padding"));
out.push("msg-shell.padding=" + gs(document.querySelector(".msg-shell"), "padding"));
out.push("msg-side.width=" + gs(document.querySelector(".msg-side"), "width"));
out.push("set-nav.width=" + gs(document.querySelector(".set-nav"), "width"));
// 主题断言(§6 机器可验):预览 iframe document 有 data-theme 与通道 link;设置页本体无
if (iframe) {
out.push("iframe-present=true");
out.push("iframe.data-theme=" + (idoc?.documentElement?.dataset?.theme ?? "NONE"));
out.push("iframe.theme-css-links=" + (idoc ? idoc.querySelectorAll("link[data-yh-theme-css]").length : -1));
out.push("iframe.init-script=" + (idoc ? idoc.querySelectorAll("script[data-yh-theme-init]").length : -1));
out.push("iframe.theme-ready=" + (idoc?.documentElement?.classList?.contains("yh-theme-ready") ?? false));
out.push("iframe.preview-root=" + (idoc ? !!idoc.querySelector(".theme-preview-root") : false));
} else {
out.push("iframe-present=false");
}
out.push("self.data-theme=" + (document.documentElement.dataset.theme ?? "NONE"));
out.push("self.theme-css-links=" + document.querySelectorAll("link[data-yh-theme-css]").length);
// 预览直开时断言主 document(即预览 document 自身)
if (router.currentRoute.value.path === "/theme-preview") {
out.push("preview.data-theme=" + (document.documentElement.dataset.theme ?? "NONE"));
out.push("preview.theme-css-links=" + document.querySelectorAll("link[data-yh-theme-css]").length);
out.push("preview.init-script=" + document.querySelectorAll("script[data-yh-theme-init]").length);
out.push("preview.theme-ready=" + document.documentElement.classList.contains("yh-theme-ready"));
}
const pre = document.createElement("pre");
pre.id = "assert-out";
pre.style.cssText = "position:fixed;left:0;bottom:0;z-index:99999;background:#fff;color:#111;font-size:12px;margin:0;padding:8px 12px;white-space:pre;";
pre.textContent = out.join("\n");
document.body.appendChild(pre);
// 完成标记:截图/外部等待循环看它
(window as any).__HARNESS_ASSERT_DONE__ = true;
document.title = "HARNESS-READY";
}
setTimeout(() => runAssert(0), 1500);
+1
View File
@@ -708,6 +708,7 @@ function deptPathOf(u: any): string {
.reverse();
return names.length > 0 ? names.join("/") : "-";
}
const profileForm = reactive({ mobile: "", email: "" });
const profileView = computed(() => {
const u: any = store.userInfo || {};
return {