add About

This commit is contained in:
Timi
2026-09-10 17:06:11 +08:00
parent e508722326
commit 3e7fe5ce14
5 changed files with 351 additions and 2 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2026 timi Copyright (c) 2026 imyeyu.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including associated documentation files (the "Software"), to deal in the Software without restriction, including
+166
View File
@@ -0,0 +1,166 @@
<template>
<div class="about-page">
<section class="brand" v-entrance-transition>
<logo />
<h1 v-text="appName" />
<p v-text="appDescription" />
</section>
<t-cell-group title="协议" theme="card" v-entrance-transition="70">
<t-cell
v-for="item in agreementItemList"
:key="item.code"
:title="item.title"
arrow
hover
@click="openAgreement(item)"
>
<template #leftIcon>
<t-icon :name="item.icon" size="1.75rem" />
</template>
</t-cell>
</t-cell-group>
<footer class="footer" v-entrance-transition="140">
<p>技术支持:夜雨</p>
<p>Copyright © 2026 imyeyu.com</p>
</footer>
</div>
</template>
<script setup lang="ts">
import { type Setting, SettingAPI } from "timi-web-api";
import Logo from "@/pages/user/component/Logo.vue";
import { getErrorMessage } from "@/utils/error";
import { Message } from "@/utils/message";
type AgreementCode = "SERVICE_LICENSE" | "PRIVACY_LICENSE" | "OPEN_SOURCE_LICENSE";
type AgreementRouteName = "ServiceAgreement" | "PrivacyAgreement" | "OpenSourceAgreement";
type AgreementItem = {
code: AgreementCode;
icon: string;
routeName: AgreementRouteName;
title: string;
};
defineOptions({
name: "About"
});
const appName = "糕店客户管理";
const appDescription = "面向门店的客户、登记、积分与经营管理工具";
const router = useRouter();
const agreementItemList: AgreementItem[] = [
{
code: "SERVICE_LICENSE",
icon: "file-attachment",
routeName: "ServiceAgreement",
title: "服务协议"
},
{
code: "PRIVACY_LICENSE",
icon: "secured",
routeName: "PrivacyAgreement",
title: "隐私协议"
},
{
code: "OPEN_SOURCE_LICENSE",
icon: "code",
routeName: "OpenSourceAgreement",
title: "开源软件协议"
}
];
const settingMap = ref<Record<string, Setting>>({});
function getAgreementId(code: AgreementCode): string {
return settingMap.value[code]?.value?.trim() || "";
}
async function openAgreement(item: AgreementItem): Promise<void> {
const articleId = getAgreementId(item.code);
if (!articleId) {
Message.warning(`${item.title}暂未配置`);
return;
}
await router.push({
name: item.routeName,
params: {
id: articleId
}
});
}
async function loadSettings(): Promise<void> {
try {
settingMap.value = await SettingAPI.map("GAO_SYSTEM");
} catch (error) {
Message.error(getErrorMessage(error, "协议配置加载失败"));
}
}
onMounted(async () => {
await loadSettings();
});
</script>
<style scoped lang="less">
.about-page {
gap: 1rem;
display: flex;
padding: calc(var(--app-nav-height) + 1rem) 0 5rem;
flex-direction: column;
.brand {
padding: 1rem;
text-align: center;
.logo {
margin: 0 auto;
}
h1 {
margin: .875rem 0 .5rem;
color: #0f172a;
font-size: 1.5rem;
font-weight: 700;
}
p {
margin: 0;
color: #52607a;
font-size: .875rem;
line-height: 1.7;
}
}
.footer {
padding: 1.25rem 1rem calc(1rem + env(safe-area-inset-bottom, 0));
text-align: center;
p {
margin: 0;
color: #52607a;
font-size: .8125rem;
line-height: 1.8;
}
}
}
:global(html.theme-dark) {
.about-page {
.brand {
h1 {
color: #e2e8f0;
}
p {
color: #94a3b8;
}
}
.footer p {
color: #94a3b8;
}
}
}
</style>
+115
View File
@@ -0,0 +1,115 @@
<template>
<div class="agreement-page">
<template v-if="article">
<header class="header" v-entrance-transition>
<h3 v-text="article.title || '协议'" />
<markdown-view
v-if="article.description"
class="description"
:content="article.description"
/>
</header>
<article-view
:article="article"
empty-text="暂无协议正文"
:resolve-attachment-url="resolveAttachmentUrl"
/>
</template>
<t-empty
v-else
:description="isLoading ? '正在加载协议' : '协议不存在'"
/>
</div>
</template>
<script setup lang="ts">
import { type Article, ArticleAPI, AttachmentAPI } from "timi-web-api";
import { ArticleView } from "timi-tdesign-mobile";
import { MarkdownView } from "timi-web";
import { getErrorMessage } from "@/utils/error";
import { Message } from "@/utils/message";
defineOptions({
name: "Agreement"
});
const route = useRoute();
const article = ref<Article>();
const isLoading = ref(false);
let loadToken = 0;
function resolveAttachmentUrl(attachmentId: string): string {
return AttachmentAPI.getReadURL(attachmentId);
}
async function loadArticle(): Promise<void> {
const requestToken = loadToken + 1;
loadToken = requestToken;
article.value = undefined;
const articleId = String(route.params.id || "").trim();
if (!articleId) {
return;
}
isLoading.value = true;
try {
const result = await ArticleAPI.detail(articleId);
if (requestToken !== loadToken) {
return;
}
article.value = result;
} catch (error) {
Message.error(getErrorMessage(error, "协议加载失败"));
} finally {
if (requestToken === loadToken) {
isLoading.value = false;
}
}
}
watch(() => route.params.id, async () => {
await loadArticle();
}, { immediate: true });
</script>
<style scoped lang="less">
.agreement-page {
padding: var(--app-nav-height) 0 1rem 0;
.header {
padding: .5rem 1rem;
h3 {
color: #0f172a;
margin: 0;
text-align: center;
line-height: 1.4;
}
.description {
color: #52607a;
font-size: .875rem;
margin-top: .5rem;
line-height: 1.7;
}
}
:deep(.content) {
border: none;
}
}
:global(html.theme-dark) {
.agreement-page {
.header {
h1 {
color: #e2e8f0;
}
.description {
color: #94a3b8;
}
}
}
}
</style>
+11 -1
View File
@@ -87,7 +87,7 @@ type MenuItem = {
type MenuGroup = { type MenuGroup = {
items: MenuItem[]; items: MenuItem[];
title: string; title?: string;
}; };
defineOptions({ defineOptions({
@@ -234,6 +234,16 @@ const menuGroupList = computed<MenuGroup[]>(() => [
path: "/management/qr-code", path: "/management/qr-code",
title: "生成客户二维码", title: "生成客户二维码",
visible: userStore.hasPermissions(permissionCodes.qrCode.create) visible: userStore.hasPermissions(permissionCodes.qrCode.create)
},
]
},
{
items: [
{
icon: "info-circle",
path: "/management/about",
title: "关于",
visible: true
} }
] ]
} }
+58
View File
@@ -1,4 +1,6 @@
import type { RouteRecordRaw } from "vue-router"; import type { RouteRecordRaw } from "vue-router";
import About from "@/pages/setting/About.vue";
import Agreement from "@/pages/setting/Agreement.vue";
import AppSettings from "@/pages/setting/AppSettings.vue"; import AppSettings from "@/pages/setting/AppSettings.vue";
import NotifyList from "@/pages/notify/NotifyList.vue"; import NotifyList from "@/pages/notify/NotifyList.vue";
import NotifyCreate from "@/pages/notify/NotifyCreate.vue"; import NotifyCreate from "@/pages/notify/NotifyCreate.vue";
@@ -146,6 +148,62 @@ export const storeRoutes: RouteRecordRaw[] = [
]; ];
export const managementSettingRoutes: RouteRecordRaw[] = [ export const managementSettingRoutes: RouteRecordRaw[] = [
{
path: "/management/about",
name: "About",
component: About,
meta: {
depth: 3,
navBarVisible: true,
navBarCanBack: true,
navBarTitle: "关于",
tabBarVisible: false,
bodyBackground: "#F5F5F8",
darkBodyBackground: "#10161f"
}
},
{
path: "/management/about/service-agreement/:id",
name: "ServiceAgreement",
component: Agreement,
meta: {
depth: 4,
navBarVisible: true,
navBarCanBack: true,
navBarTitle: "服务协议",
tabBarVisible: false,
bodyBackground: "#FFF",
darkBodyBackground: "#10161f"
}
},
{
path: "/management/about/privacy-agreement/:id",
name: "PrivacyAgreement",
component: Agreement,
meta: {
depth: 4,
navBarVisible: true,
navBarCanBack: true,
navBarTitle: "隐私协议",
tabBarVisible: false,
bodyBackground: "#FFF",
darkBodyBackground: "#10161f"
}
},
{
path: "/management/about/open-source-agreement/:id",
name: "OpenSourceAgreement",
component: Agreement,
meta: {
depth: 4,
navBarVisible: true,
navBarCanBack: true,
navBarTitle: "开源软件协议",
tabBarVisible: false,
bodyBackground: "#F5F5F8",
darkBodyBackground: "#10161f"
}
},
{ {
path: "/management/settings", path: "/management/settings",
name: "AppSettings", name: "AppSettings",