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
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
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 = {
items: MenuItem[];
title: string;
title?: string;
};
defineOptions({
@@ -234,6 +234,16 @@ const menuGroupList = computed<MenuGroup[]>(() => [
path: "/management/qr-code",
title: "生成客户二维码",
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 About from "@/pages/setting/About.vue";
import Agreement from "@/pages/setting/Agreement.vue";
import AppSettings from "@/pages/setting/AppSettings.vue";
import NotifyList from "@/pages/notify/NotifyList.vue";
import NotifyCreate from "@/pages/notify/NotifyCreate.vue";
@@ -146,6 +148,62 @@ export const storeRoutes: 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",
name: "AppSettings",