ow
This commit is contained in:
1 parent
61869f1af0
commit
9344b56334
5 files changed
+626
-10
No files matched your search
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<nav class="dash-nav">
|
||||
<a v-for="n in items" :key="n.key" class="nav-item" @click="onClick(n.key)">
|
||||
<cust-icon-font :type="n.icon" :size="22" />
|
||||
<span>{{ n.label }}</span>
|
||||
</a>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
type NavItem = { key: string; label: string; icon: string };
|
||||
|
||||
defineProps<{ items: NavItem[] }>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "jump", key: string): void;
|
||||
}>();
|
||||
|
||||
const onClick = (key: string) => emit("jump", key);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.dash-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
height: 52px;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* 宽屏时与卡片区(1080px)对齐,避免图标被拉得太散 */
|
||||
@media (min-width: 1200px) {
|
||||
.dash-nav {
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* 字号对齐 ft 体系:辅助文字 12px(同 .ft-item-label / .ft-chip) */
|
||||
.nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 4px 10px;
|
||||
color: #8a919f;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: color 0.16s;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
color: #1677ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<section class="grp-card">
|
||||
<!-- 封面:有 cover 用图片,否则用分组主题色渐变 + 大号水印图标 -->
|
||||
<div class="grp-cover" :class="{ 'has-image': !!cover, 'grp-test': !cover }" :style="coverStyle">
|
||||
<cust-icon-font v-if="!cover" class="grp-cover-mark" :type="icon" :size="132" />
|
||||
<span class="grp-badge">{{ badge || name }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 功能列表:逐行排列,左侧软色图标 + 标题副标题 + 右侧圆形分组图标 -->
|
||||
<div class="grp-list">
|
||||
<a v-for="it in items" :key="it.FunctionName" class="grp-row" :title="it.FunctionLocalName"
|
||||
@click="onClick(it.ApiRoute)">
|
||||
<span class="row-icon" :style="{ background: soft, color }">
|
||||
<cust-icon-font :type="it.Icon || icon" :size="20" />
|
||||
</span>
|
||||
<span class="row-text">
|
||||
<span class="row-title">{{ it.FunctionLocalName }}</span>
|
||||
<span v-if="subtitleOf(it)" class="row-sub">{{ subtitleOf(it) }}</span>
|
||||
</span>
|
||||
<span class="row-glyph" :style="{ background: soft, color }">
|
||||
<cust-icon-font :type="icon" :size="18" />
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import type { SecurityFunctionDisplayData } from "@/data/auto/securityFunctionDisplayData";
|
||||
|
||||
/** 单项可额外携带 subtitle,用于封面卡里的两行文案 */
|
||||
export type GroupCardItem = SecurityFunctionDisplayData & { subtitle?: string };
|
||||
|
||||
const props = defineProps<{
|
||||
name: string;
|
||||
/** 分组主题色:图标着色、无封面时的渐变封面 */
|
||||
color: string;
|
||||
/** 主题浅色:图标底色 */
|
||||
soft: string;
|
||||
icon: string;
|
||||
/** 兼容旧参数:仅作为布局提示,当前统一为纵向列表 */
|
||||
layout?: 2 | 3;
|
||||
/** 封面图片地址;缺省时用主题色渐变占位 */
|
||||
cover?: string;
|
||||
/** 封面左上角标签;缺省时显示分组名称 */
|
||||
badge?: string;
|
||||
items: GroupCardItem[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "navigate", route: string): void;
|
||||
}>();
|
||||
|
||||
const coverStyle = computed(() =>
|
||||
props.cover
|
||||
? { backgroundImage: `url(${props.cover})` }
|
||||
: { background: `linear-gradient(135deg, ${props.color} 0%, ${props.soft} 100%)` }
|
||||
);
|
||||
|
||||
/** 副标题优先取自定义 subtitle,其次回落英文名;都为空则不渲染该行 */
|
||||
const subtitleOf = (it: GroupCardItem) => (it.subtitle || it.FunctionEngName || "").trim();
|
||||
|
||||
const onClick = (route?: string) => {
|
||||
if (route) emit("navigate", route);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.grp-card {
|
||||
overflow: visible;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 18px;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* ==================== 封面 ==================== */
|
||||
.grp-cover {
|
||||
position: relative;
|
||||
aspect-ratio: 16 / 10;
|
||||
max-height: 360px;
|
||||
border-radius: 18px 18px 0 0;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
.grp-test {
|
||||
position: relative;
|
||||
aspect-ratio: 40 / 10;
|
||||
max-height: 360px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.grp-cover-mark {
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
bottom: -20px;
|
||||
z-index: 3;
|
||||
color: rgba(255, 255, 255, 0.32);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.grp-badge {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 12px;
|
||||
z-index: 2;
|
||||
padding: 2px 10px;
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, 0.28);
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
line-height: 58px;
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
/* ==================== 列表 ==================== */
|
||||
.grp-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
border-radius: 0 0 18px 18px;
|
||||
background: #b9cbee;
|
||||
}
|
||||
|
||||
.grp-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
color: inherit;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
transition:
|
||||
transform 0.16s ease,
|
||||
box-shadow 0.16s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
.row-icon {
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.row-text {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.row-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1f2329;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.row-sub {
|
||||
font-size: 12px;
|
||||
color: #8a919f;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.row-glyph {
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.grp-card {
|
||||
max-width: 1080px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<div class="hero-shell">
|
||||
<section class="hero">
|
||||
<div class="hero-inner">
|
||||
<div class="hero-top">
|
||||
<div class="brand">
|
||||
<a-avatar
|
||||
shape="square"
|
||||
:size="36"
|
||||
src="/youjia.svg"
|
||||
class="brand-mark"
|
||||
/>
|
||||
<span>有家相册</span>
|
||||
</div>
|
||||
<a-dropdown placement="bottomRight" :trigger="['click']">
|
||||
<div class="avatar" :title="userName">{{ avatarText }}</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="onUserMenuClick">
|
||||
<a-menu-item key="amentUserLocalName">
|
||||
<UserOutlined />
|
||||
{{ t("layout.header.amentUserLocalName") }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="amentPassword">
|
||||
<LockOutlined />
|
||||
{{ t("layout.header.amentPassword") }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="biometricButton">
|
||||
<LockOutlined />
|
||||
{{ biometricButtonLabel }}
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="logout">
|
||||
<LogoutOutlined />
|
||||
{{ t("layout.header.logout") }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="hero-card">
|
||||
<div class="hello">你好,{{ userName }} <span class="wave">👋</span></div>
|
||||
<div class="sub">欢迎回家,记录新美好。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<changePassword v-model:showModel="showChangePassword" />
|
||||
<changeLocalName v-model:showModel="showChangeUserLocalName" />
|
||||
<changeBiometricEnrollCard v-model:showModel="showBiometricEnroll" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { notification } from "ant-design-vue";
|
||||
import type { MenuProps } from "ant-design-vue";
|
||||
import { LockOutlined, LogoutOutlined, UserOutlined } from "@ant-design/icons-vue";
|
||||
import authService from "@/service/security/authService";
|
||||
import { useLoginProfileStore } from "@/store/modules/loginProfile";
|
||||
import i18n from "@/locales";
|
||||
import changePassword from "@/views/login/changePassword.vue";
|
||||
import changeLocalName from "@/views/login/changeLocalName.vue";
|
||||
import changeBiometricEnrollCard from "@/views/login/changeBiometricEnrollCard.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const profile = useLoginProfileStore();
|
||||
const { t } = i18n.global;
|
||||
|
||||
const userName = computed(
|
||||
() => profile.getUserLocalName() || profile.getUsername() || "新朋友"
|
||||
);
|
||||
const avatarText = computed(() => {
|
||||
const u = (userName.value || "U").trim();
|
||||
return u ? u.slice(-1).toUpperCase() : "U";
|
||||
});
|
||||
|
||||
/* ==================== 头像菜单 ==================== */
|
||||
/* 欢迎页使用独立外壳(layout/home.vue),没有原顶栏,账号操作挂到右上角头像上 */
|
||||
const showChangePassword = ref<boolean>(false);
|
||||
const showChangeUserLocalName = ref<boolean>(false);
|
||||
const showBiometricEnroll = ref<boolean>(false);
|
||||
|
||||
// Coarse platform detection so the button label is friendly on each device.
|
||||
const biometricButtonLabel = computed(() => {
|
||||
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
||||
if (/iPhone|iPad/.test(ua)) return "Sign in with Face ID / Touch ID";
|
||||
if (/Android/.test(ua)) return "Sign in with fingerprint";
|
||||
if (/Windows/.test(ua)) return "Sign in with Windows Hello";
|
||||
if (/Mac OS X/.test(ua)) return "Sign in with Touch ID";
|
||||
return "Sign in with fingerprint / face";
|
||||
});
|
||||
|
||||
const onUserMenuClick: MenuProps["onClick"] = (menuInfo) => {
|
||||
if (menuInfo.key === "amentUserLocalName") {
|
||||
showChangeUserLocalName.value = true;
|
||||
} else if (menuInfo.key === "amentPassword") {
|
||||
showChangePassword.value = true;
|
||||
} else if (menuInfo.key === "biometricButton") {
|
||||
showBiometricEnroll.value = true;
|
||||
} else if (menuInfo.key === "logout") {
|
||||
logout();
|
||||
}
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
await authService.Logout();
|
||||
profile.clearStorage();
|
||||
notification.info({
|
||||
message: t("layout.notification.message"),
|
||||
description: t("layout.notification.description"),
|
||||
placement: "topRight",
|
||||
});
|
||||
router.push("/");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
/* 渐变头图:只承载顶栏(品牌+头像),不再包含卡片,避免被卡片内容撑高 */
|
||||
.hero {
|
||||
padding: 18px 16px 36px;
|
||||
border-radius: 0 0 22px 22px;
|
||||
background: linear-gradient(135deg, #1677ff 0%, #36cfc9 100%);
|
||||
color: #fff;
|
||||
box-shadow: 0 6px 18px rgba(22, 119, 255, 0.22);
|
||||
}
|
||||
|
||||
/* hero 的外壳,把 hero 与 hero-card 编为一组,作为页面里的非伸缩块 */
|
||||
.hero-shell {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.hero-inner {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 上层半透明卡片:放在 hero 外面,用负 margin-top 叠到 hero 底部。
|
||||
顶部与 hero 重叠处保持半透明让渐变透出,下方不透明避免露出页面灰底 */
|
||||
.hero-card {
|
||||
margin: -24px 16px 18px;
|
||||
padding: 20px 18px;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.55) 0%,
|
||||
rgba(255, 255, 255, 0.6) 30%,
|
||||
rgba(255, 255, 255, 0.95) 45%,
|
||||
rgba(255, 255, 255, 0.96) 100%
|
||||
);
|
||||
backdrop-filter: blur(18px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(160%);
|
||||
box-shadow:
|
||||
0 14px 34px rgba(15, 23, 42, 0.14),
|
||||
0 4px 12px rgba(15, 23, 42, 0.08);
|
||||
color: #1f2329;
|
||||
}
|
||||
|
||||
.hero-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 9px;
|
||||
background: rgba(255, 255, 255, 0.22);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
color: #1677ff;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.hello {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.wave {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.sub {
|
||||
margin-top: 10px;
|
||||
margin-left: 60px;
|
||||
font-size: 20px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.hero {
|
||||
padding: 22px 20px 40px;
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
margin: -28px 20px 22px;
|
||||
padding: 22px 22px;
|
||||
}
|
||||
|
||||
.hello {
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
/* 宽屏下把 hero + card 整体居中到 1080,避免渐变条在大屏拉得过宽 */
|
||||
.hero-shell {
|
||||
max-width: 1080px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.hero-inner {
|
||||
max-width: 1080px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 24px 24px 44px;
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
margin: -28px 24px 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="home-layout">
|
||||
<!-- 内容区:整屏除底部信息条外全部留给页面 -->
|
||||
<div class="home-viewport">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { WebSitePolicy } from "@/policy/custom/webSitePolicy";
|
||||
|
||||
/**
|
||||
* 欢迎页(/dashboard)专用外壳。
|
||||
* 不套用后台的侧边菜单 / 顶栏 / 面包屑,整屏交给页面自己排版:
|
||||
* home-layout(列:内容 + 信息条,100dvh 不出现窗口滚动条)
|
||||
* └ home-viewport(自适应剩余高度,内容由页面内部滚动)
|
||||
*/
|
||||
const copy: string = WebSitePolicy.Copy;
|
||||
const beian: string = WebSitePolicy.Beian;
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.home-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
overflow: hidden;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.home-viewport {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.home-footer {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 10px;
|
||||
padding: 6px 12px;
|
||||
border-top: 1px solid #eef0f4;
|
||||
background: #fff;
|
||||
color: #8a919f;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
color: #8a919f;
|
||||
text-decoration: none;
|
||||
transition: color 0.16s;
|
||||
|
||||
&:hover {
|
||||
color: #1677ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -7,8 +7,8 @@
|
||||
<!-- 功能分组:卡片外壳 / 标题字号沿用 ft-page 体系 -->
|
||||
<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" :items="grp.items"
|
||||
@navigate="onpush" />
|
||||
:color="grp.color" :soft="grp.soft" :icon="grp.icon" :layout="grp.layout" :cover="grp.cover"
|
||||
:badge="grp.badge" :items="grp.items" @navigate="onpush" />
|
||||
|
||||
<a-empty v-if="!groups.length && !loading" description="没有匹配的功能" />
|
||||
</div>
|
||||
@@ -27,7 +27,7 @@ import { useRouter } from "vue-router";
|
||||
import securityUserService from "@/service/security/securityUserService";
|
||||
import type { SecurityFunctionDisplayData } from "@/data/auto/securityFunctionDisplayData";
|
||||
import DashboardHero from "@/components/dashboard/DashboardHero.vue";
|
||||
import DashboardGroupCard from "@/components/dashboard/DashboardGroupCard.vue";
|
||||
import DashboardGroupCard, { type GroupCardItem } from "@/components/dashboard/DashboardGroupCard.vue";
|
||||
import DashboardBottomNav from "@/components/dashboard/DashboardBottomNav.vue";
|
||||
|
||||
interface NavItem {
|
||||
@@ -51,13 +51,18 @@ const groupDefs: {
|
||||
soft: string;
|
||||
icon: string;
|
||||
layout: 2 | 3;
|
||||
/** 封面图片地址(可选),缺省时由卡片用主题色渐变占位 */
|
||||
cover?: string;
|
||||
/** 封面左上角标签(可选),缺省时显示分组名称 */
|
||||
badge?: string;
|
||||
}[] = [
|
||||
{ key: "disk", name: "网盘", color: "#1677ff", soft: "#e6f4ff", icon: "dbank", layout: 2 },
|
||||
{ key: "share", name: "共享中心", color: "#fa8c16", soft: "#fff7e6", icon: "dgroup", layout: 2 },
|
||||
{ key: "disk", name: "网盘", color: "#1677ff", soft: "#e6f4ff", icon: "dbank", layout: 2},
|
||||
{ key: "share", name: "共享中心", color: "#fa8c16", soft: "#fff7e6", icon: "dgroup", layout: 2},
|
||||
// { key: "photo", name: "相册", color: "#722ed1", soft: "#f9f0ff", icon: "picture", layout: 3, cover: "/images/main/p4.png" },
|
||||
{ key: "photo", name: "相册", color: "#722ed1", soft: "#f9f0ff", icon: "picture", layout: 3 },
|
||||
{ key: "audio", name: "音乐", color: "#eb2f96", soft: "#fff0f6", icon: "foldermp3", layout: 3 },
|
||||
{ key: "audio", name: "音乐", color: "#eb2f96", soft: "#fff0f6", icon: "foldermp3", layout: 3},
|
||||
{ key: "video", name: "视频", color: "#13c2c2", soft: "#e6fffb", icon: "foldervideo", layout: 3 },
|
||||
{ key: "family", name: "族谱管理", color: "#52c41a", soft: "#f6ffed", icon: "family", layout: 2 },
|
||||
{ key: "family", name: "族谱管理", color: "#52c41a", soft: "#f6ffed", icon: "family", layout: 2},
|
||||
{ key: "other", name: "更多", color: "#8a919f", soft: "#f5f6f8", icon: "gailan", layout: 3 },
|
||||
];
|
||||
|
||||
@@ -86,19 +91,46 @@ const GROUP_OF: Record<string, GroupKey> = {
|
||||
const resolveGroup = (it: SecurityFunctionDisplayData): GroupKey =>
|
||||
GROUP_OF[it.FunctionName] ?? "other";
|
||||
|
||||
/* ==================== 自定义副标题 ==================== */
|
||||
/** 留空时卡片会自动回退到 FunctionEngName */
|
||||
const SUBTITLE_OF: Record<string, string> = {
|
||||
DmsFileEnquiry: "上传、下载",
|
||||
DmsFileDeleteEnquiry: "",
|
||||
FamilyGroupShare: "",
|
||||
FamilyShareOperation: "",
|
||||
FamilyGroupOperation: "",
|
||||
FamilyGroupEnquiry: "",
|
||||
Preview: "",
|
||||
PublishPhotoOperation: "",
|
||||
PublishPhotoEnquiry: "",
|
||||
PreviewAudio: "播放从默认专辑",
|
||||
PublishAudioOperation: "",
|
||||
PublishAudioEnquiry: "",
|
||||
PreviewVideo: "",
|
||||
PublishVideoOperation: "",
|
||||
PublishVideoEnquiry: "",
|
||||
FamilyPersonEnquiry: "",
|
||||
FamilyPersonOperation: "",
|
||||
FamilyTreeEnquiry: "",
|
||||
FamilyTreeOperation: "",
|
||||
};
|
||||
|
||||
const resolveSubtitle = (it: SecurityFunctionDisplayData): string =>
|
||||
SUBTITLE_OF[it.FunctionName] ?? "";
|
||||
|
||||
/* ==================== 分组结果 ==================== */
|
||||
const groups = computed(() => {
|
||||
const taken = new Set<string>();
|
||||
const result: { key: GroupKey; name: string; color: string; soft: string; icon: string; layout: 2 | 3; items: SecurityFunctionDisplayData[] }[] = [];
|
||||
const result: { key: GroupKey; name: string; color: string; soft: string; icon: string; layout: 2 | 3; cover?: string; badge?: string; items: GroupCardItem[] }[] = [];
|
||||
|
||||
for (const g of groupDefs) {
|
||||
const items: SecurityFunctionDisplayData[] = [];
|
||||
const items: GroupCardItem[] = [];
|
||||
for (const it of allItems.value) {
|
||||
if (resolveGroup(it) !== g.key) continue;
|
||||
const label = it.FunctionLocalName || it.FunctionName;
|
||||
if (taken.has(label)) continue;
|
||||
taken.add(label);
|
||||
items.push(it);
|
||||
items.push({ ...it, subtitle: resolveSubtitle(it) });
|
||||
}
|
||||
if (items.length) result.push({ ...g, items });
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user