This commit is contained in:
itcsx committed 2026-09-19 10:24:21 +08:00
1 parent 9344b56334
commit 441d1e821e
37 files changed
+3706 -23

No files matched your search

+72 -6
View File
@@ -8,11 +8,16 @@
<div class="page-main ft-page">
<DashboardGroupCard v-for="grp in groups" :id="'grp-' + grp.key" :key="grp.key" :name="grp.name"
:color="grp.color" :soft="grp.soft" :icon="grp.icon" :layout="grp.layout" :cover="grp.cover"
:badge="grp.badge" :items="grp.items" @navigate="onpush" />
:badge="grp.badge" :items="grp.items" @navigate="onpush" @help="onHelp" />
<a-empty v-if="!groups.length && !loading" description="没有匹配的功能" />
</div>
<!-- 新手帮助:统一挂在页面上,由行尾图标触发,按 FunctionName 分发不同文档 -->
<HelpTimelineModal v-model:open="helpOpen" :function-name="helpTarget?.FunctionName"
:title="helpTarget?.FunctionLocalName || helpTarget?.FunctionName" :color="helpColor" :soft="helpSoft"
:icon="helpIcon" />
<!-- 底部快捷栏:与其它页面一致,固定在底部 -->
<div class="page-footer dash-footer">
<DashboardBottomNav :items="navItems" @jump="onNavClick" />
@@ -29,6 +34,7 @@ import type { SecurityFunctionDisplayData } from "@/data/auto/securityFunctionDi
import DashboardHero from "@/components/dashboard/DashboardHero.vue";
import DashboardGroupCard, { type GroupCardItem } from "@/components/dashboard/DashboardGroupCard.vue";
import DashboardBottomNav from "@/components/dashboard/DashboardBottomNav.vue";
import HelpTimelineModal from "@/components/basic/HelpTimelineModal.vue";
interface NavItem {
key: string;
@@ -94,7 +100,7 @@ const resolveGroup = (it: SecurityFunctionDisplayData): GroupKey =>
/* ==================== 自定义副标题 ==================== */
/** 留空时卡片会自动回退到 FunctionEngName */
const SUBTITLE_OF: Record<string, string> = {
DmsFileEnquiry: "上传、下载",
DmsFileEnquiry: "",
DmsFileDeleteEnquiry: "",
FamilyGroupShare: "",
FamilyShareOperation: "",
@@ -103,7 +109,7 @@ const SUBTITLE_OF: Record<string, string> = {
Preview: "",
PublishPhotoOperation: "",
PublishPhotoEnquiry: "",
PreviewAudio: "播放从默认专辑",
PreviewAudio: "",
PublishAudioOperation: "",
PublishAudioEnquiry: "",
PreviewVideo: "",
@@ -127,7 +133,7 @@ const groups = computed(() => {
const items: GroupCardItem[] = [];
for (const it of allItems.value) {
if (resolveGroup(it) !== g.key) continue;
const label = it.FunctionLocalName || it.FunctionName;
const label = it.FunctionLocalName || it.FunctionEngName;
if (taken.has(label)) continue;
taken.add(label);
items.push({ ...it, subtitle: resolveSubtitle(it) });
@@ -149,6 +155,36 @@ const navItems = computed<NavItem[]>(() =>
})
);
/**
* 按左侧菜单的顺序排列。
*
* Orderby 只在「同一个父节点下」才是连续有意义的序号,跨父组直接比大小会错:
* 某个父组(比如从「有家网盘」拆出来的组)内部可能从 0 重新计数,
* 它的子项就会被平铺排序甩到全局最前面。
*
* 所以这里先按父节点自身的序号把「组」排好,再在组内按 Orderby 排,
* 平铺出来的先后就和菜单的树形展开顺序一致。
*/
const sortByMenuOrder = (list: SecurityFunctionDisplayData[]): SecurityFunctionDisplayData[] => {
// 每个节点自己的序号,用作它作为「父」时的排序依据
const parentOrder = new Map<string, number>();
list.forEach((it) => {
if (it.FunctionName) parentOrder.set(it.FunctionName, it.Orderby ?? 0);
});
/** 有父节点就跟着父走,没有(顶层)就用自己的序号 */
const groupOrder = (it: SecurityFunctionDisplayData): number => {
if (it.FunctionNameParent && parentOrder.has(it.FunctionNameParent)) {
return parentOrder.get(it.FunctionNameParent)!;
}
return it.Orderby ?? 0;
};
return [...list].sort(
(a, b) => groupOrder(a) - groupOrder(b) || (a.Orderby ?? 0) - (b.Orderby ?? 0)
);
};
/* ==================== 交互 ==================== */
const onpush = (apiRoute: string) => {
if (apiRoute) router.push(apiRoute);
@@ -157,13 +193,43 @@ const onNavClick = (key: string) => {
document.getElementById("grp-" + key)?.scrollIntoView({ behavior: "smooth", block: "start" });
};
/* ==================== 新手帮助弹窗 ==================== */
const helpOpen = ref(false);
/** 当前请求帮助的功能项,决定 HelpTimelineModal 加载哪份文档 */
const helpTarget = ref<GroupCardItem | null>(null);
/** 帮助弹窗沿用所属分组的主题色,与卡片保持视觉一致 */
const helpTheme = computed(() => {
const key = helpTarget.value ? resolveGroup(helpTarget.value) : undefined;
return groupDefs.find(g => g.key === key) ?? groupDefs[groupDefs.length - 1];
});
const helpColor = computed(() => helpTheme.value.color);
const helpSoft = computed(() => helpTheme.value.soft);
const helpIcon = computed(() => helpTheme.value.icon);
const onHelp = (it: GroupCardItem) => {
helpTarget.value = it;
helpOpen.value = true;
};
onMounted(async () => {
loading.value = true;
try {
const fd = await securityUserService.FindMenuByUsername();
const raw = fd?.SecurityFunctionDisplayDatas ?? [];
allItems.value = raw.filter(
it => it.FunctionIndc !== "N" && (it.PageUrl?.indexOf("master") ?? -1) > 0
// 有些功能会挂在「不存在的父节点」下(例如测试残留的 abc),
// 它和正式记录同名却属于另一条,按 Orderby 排序时会窜到最前面,
// 表现为首页位置与左侧菜单对不上、改了后台顺序也不生效。
// 这里剔掉这类孤立记录,只保留父节点真实存在的。
const knownFunctions = new Set(raw.map(it => it.FunctionName));
allItems.value = sortByMenuOrder(
raw.filter(
it =>
it.FunctionIndc !== "N" &&
(it.PageUrl?.indexOf("master") ?? -1) > 0 &&
(!it.FunctionNameParent || knownFunctions.has(it.FunctionNameParent))
)
);
} finally {
loading.value = false;
@@ -99,6 +99,13 @@
<a-input size="large" placeholder="请输入页面名称" :readonly="!isEdit"
v-model:value="data.SecurityFunction.PageName" />
</div>
<!-- 备注:内容可能较长,占满整行显示 -->
<div class="ft-field ft-field-full">
<div class="ft-label">备注</div>
<a-textarea :auto-size="{ minRows: 3, maxRows: 10 }" :maxlength="1000"
placeholder="请输入备注" :readonly="!isEdit"
v-model:value="data.SecurityFunction.Remark" />
</div>
</div>
</div>