release: freeze demo v1.1.0 2026-10-19
CI/CD Pipeline / Backend Build & Test (push) Canceled after 0s
CI/CD Pipeline / Admin Web Build (push) Canceled after 0s
CI/CD Pipeline / Mini Program Compile Check (push) Canceled after 0s
CI/CD Pipeline / Migration Validation (push) Canceled after 0s
CI/CD Pipeline / Test Shell Guard (Phase 23 Gate) (push) Canceled after 0s
CI/CD Pipeline / UI Contracts (source-structure gates) (push) Canceled after 0s
CI/CD Pipeline / E2E (live stack) (push) Canceled after 0s

This commit is contained in:
WorkBuddy
2026-10-19 20:36:22 +08:00
parent 8b505d9133
commit 69ebf33e18
157 changed files with 21111 additions and 72 deletions
+118
View File
@@ -0,0 +1,118 @@
<template>
<PageLayout title="关于我们">
<div class="about">
<div class="about__hero">
<div class="about__logo">善</div>
<div class="about__name">善缘丹阳</div>
<div class="about__slogan">公益婚恋服务平台</div>
<div class="about__version">版本 v1.1.0 · 演示端</div>
</div>
<UiSectionHeader title="平台简介" />
<div class="about__card">
<p class="about__text">
善缘丹阳公益婚恋服务平台由丹阳市文明办、团委、妇联等多部门联合推动,面向丹阳市成年单身人士提供实名制、公益性的婚恋交友服务。
</p>
<p class="about__text">
平台采用实名认证 + 人工审核机制,确保用户真实身份;所有服务永久免费,不收取任何费用。
</p>
</div>
<UiSectionHeader title="联系我们" />
<van-cell-group inset>
<van-cell title="客服电话" value="0511-80000000" />
<van-cell title="服务时间" value="工作日 9:00-17:00" />
<van-cell title="隐私政策" is-link @click="router.push('/settings/privacy')" />
<van-cell title="帮助中心" is-link @click="router.push('/help')" />
</van-cell-group>
<p class="about__free">本平台为公益服务,所有功能永久免费</p>
<p class="about__footer">© 2026 丹阳市文明办 · 共青团丹阳市委 · 丹阳市妇联</p>
</div>
</PageLayout>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import PageLayout from '@/layouts/PageLayout.vue'
import UiSectionHeader from '@/components/UiSectionHeader.vue'
const router = useRouter()
</script>
<style scoped>
.about {
padding-bottom: 24px;
}
.about__hero {
background: var(--mini-brand-gradient);
color: #fff;
text-align: center;
padding: 28px 16px 24px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
}
.about__logo {
width: 56px;
height: 56px;
margin: 0 auto 10px;
border-radius: 16px;
background: rgba(255, 255, 255, 0.18);
display: flex;
align-items: center;
justify-content: center;
font-size: 26px;
font-weight: 600;
}
.about__name {
font-size: 18px;
font-weight: 600;
letter-spacing: 1px;
}
.about__slogan {
font-size: 12px;
opacity: 0.85;
margin-top: 4px;
}
.about__version {
font-size: 11px;
opacity: 0.7;
margin-top: 8px;
}
.about :deep(.ui-section-header) {
margin-left: 16px;
margin-right: 16px;
}
.about__card {
margin: 0 16px;
background: var(--mini-surface);
border-radius: var(--mini-radius);
box-shadow: var(--mini-shadow);
padding: 14px 16px;
}
.about__text {
margin: 0;
font-size: 13px;
line-height: 1.8;
color: var(--mini-text);
}
.about__text + .about__text {
margin-top: 10px;
}
.about :deep(.van-cell-group--inset) {
margin: 0 16px;
}
.about__free {
text-align: center;
font-size: 12px;
color: var(--mini-text-secondary);
margin: 20px 0 0;
}
.about__footer {
text-align: center;
font-size: 11px;
color: var(--mini-text-muted);
margin: 8px 16px 0;
line-height: 1.6;
}
</style>
@@ -0,0 +1,134 @@
<template>
<PageLayout title="黑名单管理">
<div class="blacklist">
<UiLoading v-if="loading" />
<UiErrorState v-else-if="error" @retry="load" />
<template v-else>
<template v-if="list.length">
<div v-for="item in list" :key="item.userId" class="blacklist__item">
<UiAvatar :src="item.avatarUrl" :name="item.userName || '用户'" :size="48" />
<div class="blacklist__info">
<div class="blacklist__name">{{ item.userName || '用户' }}</div>
<div class="blacklist__meta">拉黑于 {{ item.blockedAtText || '未知时间' }}</div>
<div v-if="item.reason" class="blacklist__reason">原因:{{ item.reason }}</div>
</div>
<van-button
size="small"
plain
type="danger"
:loading="actionLoadingUserId === item.userId"
@click="onUnblock(item)"
>
解除拉黑
</van-button>
</div>
</template>
<UiEmpty v-else description="黑名单是空的,你还没有拉黑过任何人" />
<p class="blacklist__hint">拉黑后对方将无法与你互动,解除拉黑后对方可重新与你互动。</p>
</template>
</div>
</PageLayout>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { showConfirmDialog } from 'vant'
import { matchService } from '@/api/services'
import { useAppStore } from '@/stores/app'
import PageLayout from '@/layouts/PageLayout.vue'
import UiLoading from '@/components/UiLoading.vue'
import UiEmpty from '@/components/UiEmpty.vue'
import UiErrorState from '@/components/UiErrorState.vue'
import UiAvatar from '@/components/UiAvatar.vue'
const appStore = useAppStore()
const loading = ref(true)
const error = ref(false)
const actionLoadingUserId = ref('')
const list = ref<any[]>([])
function pick(result: any): any[] {
if (Array.isArray(result)) return result
return result?.items || result?.records || []
}
async function load() {
loading.value = true
error.value = false
try {
const data = await matchService.getBlockedList({ pageSize: 20 })
list.value = pick(data)
} catch {
error.value = true
} finally {
loading.value = false
}
}
async function onUnblock(item: any) {
if (!item?.userId || actionLoadingUserId.value) return
try {
await showConfirmDialog({
title: '解除拉黑',
message: `解除后「${item.userName || '对方'}」可重新与你互动。是否继续?`,
})
} catch {
return
}
actionLoadingUserId.value = item.userId
try {
await matchService.unblock(item.userId)
list.value = list.value.filter((u) => u.userId !== item.userId)
appStore.toastSuccess('已解除拉黑')
} catch (err: any) {
appStore.toastError(err?.message || '操作失败')
} finally {
actionLoadingUserId.value = ''
}
}
onMounted(load)
</script>
<style scoped>
.blacklist {
padding: 12px 16px 24px;
}
.blacklist__item {
display: flex;
align-items: center;
gap: 12px;
background: var(--mini-surface);
border-radius: var(--mini-radius);
box-shadow: var(--mini-shadow);
padding: 12px;
margin-bottom: 10px;
}
.blacklist__info {
flex: 1;
min-width: 0;
}
.blacklist__name {
font-size: 15px;
font-weight: 600;
color: var(--mini-text-strong);
}
.blacklist__meta {
font-size: 12px;
color: var(--mini-text-secondary);
margin-top: 2px;
}
.blacklist__reason {
font-size: 12px;
color: var(--mini-text-muted);
margin-top: 2px;
}
.blacklist__hint {
margin: 8px 0 0;
font-size: 12px;
color: var(--mini-text-muted);
line-height: 1.6;
}
</style>
@@ -0,0 +1,120 @@
<template>
<PageLayout title="帮助中心">
<div class="help">
<van-tabs v-model:active="activeCategory" sticky>
<van-tab v-for="cat in FAQ_DATA" :key="cat.category" :name="cat.category" :title="cat.category" />
</van-tabs>
<div class="help__body">
<van-collapse v-model="expanded" accordion>
<van-collapse-item
v-for="(faq, idx) in currentItems"
:key="faq.q"
:name="`${activeCategory}-${idx}`"
:title="faq.q"
>
{{ faq.a }}
</van-collapse-item>
</van-collapse>
</div>
<div class="help__footer">
<van-cell-group inset>
<van-cell title="客服电话" value="0511-80000000" />
<van-cell title="服务时间" value="工作日 9:00-17:00" />
</van-cell-group>
<p class="help__free">本平台为公益服务,所有功能及咨询永久免费</p>
</div>
</div>
</PageLayout>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import PageLayout from '@/layouts/PageLayout.vue'
interface FaqItem {
q: string
a: string
}
interface FaqCategory {
category: string
items: FaqItem[]
}
const FAQ_DATA: FaqCategory[] = [
{
category: '账号与登录',
items: [
{ q: '如何注册账号?', a: '点击「微信一键登录」,系统将自动创建账号并完成实名认证。' },
{ q: '忘记登录密码怎么办?', a: '本平台使用微信授权登录,无需设置密码。如遇到问题请重新登录。' },
{ q: '如何绑定手机号?', a: '登录后在「我的」页面完成实名认证即可绑定手机号。' },
],
},
{
category: '推荐与匹配',
items: [
{ q: '推荐列表是怎么生成的?', a: '系统根据您的择偶条件和已有用户资料,按照匹配算法每日生成推荐列表。' },
{ q: '为什么收不到推荐?', a: '请检查您的资料完整度是否高于60%,以及择偶条件是否合理(不宜过窄)。' },
{ q: '如何暂停推荐?', a: '在「设置」中切换关系状态为「已暂停寻找」即可停止推荐。' },
{ q: '可以查看谁喜欢过我吗?', a: '可以,在首页右上角点击「喜欢我的人」即可查看。' },
],
},
{
category: '活动参与',
items: [
{ q: '如何报名参加活动?', a: '在活动详情页点击「立即报名」,填写紧急联系人信息后提交。' },
{ q: '活动报名后能否取消?', a: '可以,在「我的活动」中找到对应活动并取消报名。' },
{ q: '活动是否需要审核?', a: '部分活动需要公益组织审核后参与,审核结果将在活动开始前通知您。' },
],
},
{
category: '隐私与安全',
items: [
{ q: '平台如何保护我的隐私?', a: '所有敏感信息(身份证、手机号)均加密存储,仅授权工作人员在案件上下文中可有限访问。' },
{ q: '如何设置隐私权限?', a: '在「隐私设置」中可以控制是否在推荐中可见、是否允许活动邀请等。' },
{ q: '如何拉黑某人?', a: '在推荐或缘分列表中点击该用户卡片,选择「拉黑」即可。拉黑后对方将不再看到您。' },
],
},
{
category: '公益红娘',
items: [
{ q: '什么是公益红娘服务?', a: '公益红娘是由丹阳市公益组织指派的专业人士,为您提供一对一的婚恋咨询服务。' },
{ q: '如何申请红娘服务?', a: '在首页点击「申请公益红娘」,填写服务需求后等待工作人员联系。' },
{ q: '红娘服务收费吗?', a: '不收费。本平台是公益性质的免费婚恋服务平台。' },
],
},
]
const activeCategory = ref(FAQ_DATA[0].category)
const expanded = ref<string>('')
const currentItems = computed<FaqItem[]>(
() => FAQ_DATA.find((c) => c.category === activeCategory.value)?.items || [],
)
</script>
<style scoped>
.help {
padding-bottom: 24px;
}
.help__body {
padding: 12px 16px 0;
}
.help__body :deep(.van-collapse) {
border-radius: var(--mini-radius);
overflow: hidden;
}
.help__footer {
padding: 16px 0 0;
}
.help__footer :deep(.van-cell-group--inset) {
margin: 0 16px;
}
.help__free {
text-align: center;
font-size: 11px;
color: var(--mini-text-muted);
margin: 16px 0 0;
}
</style>
@@ -0,0 +1,203 @@
<template>
<PageLayout title="通知">
<div class="notif">
<div class="notif__bar">
<span class="notif__count">{{ unreadCount > 0 ? `${unreadCount} 条未读` : '暂无未读' }}</span>
<van-button
v-if="unreadCount > 0"
size="small"
plain
type="primary"
:loading="markingAll"
@click="onMarkAll"
>
全部已读
</van-button>
</div>
<UiLoading v-if="loading" />
<UiErrorState v-else-if="error" @retry="load" />
<template v-else>
<template v-if="list.length">
<div
v-for="item in list"
:key="item.id"
class="notif__item"
:class="{ 'notif__item--unread': !isRead(item) }"
@click="onMarkRead(item)"
>
<span v-if="!isRead(item)" class="notif__dot" />
<div class="notif__main">
<div class="notif__head">
<span class="notif__title">{{ item.title }}</span>
<span class="notif__time">{{ formatDateTime(item.createdAt) }}</span>
</div>
<p class="notif__content">{{ item.content }}</p>
<UiTag v-if="typeText(item.type)" type="info">{{ typeText(item.type) }}</UiTag>
</div>
</div>
</template>
<UiEmpty v-else description="暂无通知" />
</template>
</div>
</PageLayout>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { notificationService } from '@/api/services'
import { useAppStore } from '@/stores/app'
import { formatDateTime } from '@/utils/datetime'
import PageLayout from '@/layouts/PageLayout.vue'
import UiLoading from '@/components/UiLoading.vue'
import UiEmpty from '@/components/UiEmpty.vue'
import UiErrorState from '@/components/UiErrorState.vue'
import UiTag from '@/components/UiTag.vue'
const appStore = useAppStore()
const TYPE_TEXT: Record<string, string> = {
system: '系统通知',
activity: '活动提醒',
match: '匹配消息',
appointment: '预约提醒',
counseling: '咨询提醒',
review: '审核通知',
}
const loading = ref(true)
const error = ref(false)
const markingAll = ref(false)
const list = ref<any[]>([])
const unreadCount = ref(0)
function pick(result: any): any[] {
if (Array.isArray(result)) return result
return result?.items || result?.records || result?.notifications || []
}
function isRead(item: any): boolean {
return item?.read === true || item?.readStatus === 'read'
}
function typeText(type?: string): string {
return type ? TYPE_TEXT[type] || '' : ''
}
const PAGE_SIZE = 20
async function load() {
loading.value = true
error.value = false
try {
const data = await notificationService.getNotifications({ limit: PAGE_SIZE, offset: 0 })
list.value = pick(data)
unreadCount.value = list.value.filter((n) => !isRead(n)).length
try {
const countData: any = await notificationService.getNotificationsUnreadCount()
const count = countData?.count ?? countData?.unreadCount
if (typeof count === 'number') unreadCount.value = count
} catch {
/* 以列表计算值兜底 */
}
} catch {
error.value = true
} finally {
loading.value = false
}
}
async function onMarkRead(item: any) {
if (!item?.id || isRead(item)) return
try {
await notificationService.markNotificationRead(item.id)
item.readStatus = 'read'
item.read = true
unreadCount.value = Math.max(0, unreadCount.value - 1)
} catch (err: any) {
appStore.toastError(err?.message || '操作失败')
}
}
async function onMarkAll() {
if (markingAll.value) return
markingAll.value = true
try {
await notificationService.markAllNotificationsRead()
list.value.forEach((n) => {
n.readStatus = 'read'
n.read = true
})
unreadCount.value = 0
appStore.toastSuccess('已全部标记为已读')
} catch (err: any) {
appStore.toastError(err?.message || '操作失败')
} finally {
markingAll.value = false
}
}
onMounted(load)
</script>
<style scoped>
.notif {
padding: 12px 16px 24px;
}
.notif__bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.notif__count {
font-size: 13px;
color: var(--mini-text-secondary);
}
.notif__item {
display: flex;
gap: 8px;
background: var(--mini-surface);
border-radius: var(--mini-radius);
box-shadow: var(--mini-shadow);
padding: 12px;
margin-bottom: 10px;
}
.notif__item--unread {
border-left: 3px solid var(--mini-accent-pink);
}
.notif__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--mini-accent-pink);
margin-top: 6px;
flex: none;
}
.notif__main {
flex: 1;
min-width: 0;
}
.notif__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
}
.notif__title {
font-size: 14px;
font-weight: 600;
color: var(--mini-text-strong);
}
.notif__time {
font-size: 11px;
color: var(--mini-text-muted);
flex: none;
}
.notif__content {
margin: 6px 0 6px;
font-size: 13px;
line-height: 1.6;
color: var(--mini-text);
}
</style>
@@ -0,0 +1,111 @@
<template>
<PageLayout title="隐私设置">
<div class="privacy">
<UiLoading v-if="loading" />
<UiErrorState v-else-if="error" @retry="load" />
<template v-else>
<van-cell-group inset>
<van-cell title="在推荐中可见" label="开启后您的资料将进入系统推荐池" center>
<template #right-icon>
<van-switch v-model="form.visibleInRecommendations" />
</template>
</van-cell>
<van-cell title="允许活动邀请" label="允许工作人员邀请您参加线下活动" center>
<template #right-icon>
<van-switch v-model="form.allowActivityInvitation" />
</template>
</van-cell>
<van-cell title="显示最近上线" label="对方可以看到您最近一次上线时间" center>
<template #right-icon>
<van-switch v-model="form.showLastOnline" />
</template>
</van-cell>
</van-cell-group>
<p class="privacy__hint">关闭「在推荐中可见」后,您的资料将不再进入系统推荐池。</p>
<div class="privacy__actions">
<van-button type="primary" block round :loading="saving" @click="save">保存设置</van-button>
</div>
</template>
</div>
</PageLayout>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { profileService } from '@/api/services'
import { useAppStore } from '@/stores/app'
import PageLayout from '@/layouts/PageLayout.vue'
import UiLoading from '@/components/UiLoading.vue'
import UiErrorState from '@/components/UiErrorState.vue'
const appStore = useAppStore()
const loading = ref(true)
const error = ref(false)
const saving = ref(false)
const form = reactive({
visibleInRecommendations: true,
allowActivityInvitation: true,
showLastOnline: false,
})
async function load() {
loading.value = true
error.value = false
try {
const data: any = await profileService.getPrivacySettings()
form.visibleInRecommendations = data?.visibleInRecommendations !== false
form.allowActivityInvitation = data?.allowActivityInvitation !== false
form.showLastOnline = data?.showLastOnline === true
} catch {
error.value = true
} finally {
loading.value = false
}
}
async function save() {
if (saving.value) return
saving.value = true
try {
const data: any = await profileService.updatePrivacySettings({
visibleInRecommendations: form.visibleInRecommendations,
allowActivityInvitation: form.allowActivityInvitation,
showLastOnline: form.showLastOnline,
})
if (data && typeof data === 'object') {
form.visibleInRecommendations = data.visibleInRecommendations !== false
form.allowActivityInvitation = data.allowActivityInvitation !== false
form.showLastOnline = data.showLastOnline === true
}
appStore.toastSuccess('保存成功')
} catch (err: any) {
appStore.toastError(err?.message || '保存失败')
} finally {
saving.value = false
}
}
onMounted(load)
</script>
<style scoped>
.privacy {
padding: 12px 0 24px;
}
.privacy :deep(.van-cell-group--inset) {
margin: 0 16px;
}
.privacy__hint {
margin: 12px 16px 0;
font-size: 12px;
color: var(--mini-text-muted);
line-height: 1.6;
}
.privacy__actions {
padding: 20px 16px 0;
}
</style>
@@ -0,0 +1,237 @@
<template>
<PageLayout title="婚恋状态">
<div class="rel">
<UiLoading v-if="loading" />
<UiErrorState v-else-if="error" @retry="load" />
<template v-else>
<!-- 当前状态 -->
<div class="rel__card">
<div class="rel__card-head">
<span class="rel__card-label">当前状态</span>
<UiTag :type="meta.kind">{{ meta.label }}</UiTag>
</div>
<p class="rel__card-desc">{{ meta.desc }}</p>
</div>
<!-- 正在了解的对象 -->
<template v-if="matchingWith">
<UiSectionHeader title="正在了解的对象" />
<div class="rel__partner">
<UiAvatar :src="matchingWith.avatarUrl" :name="matchingWith.displayName || '对方'" :size="48" />
<div class="rel__partner-main">
<div class="rel__partner-name">{{ matchingWith.displayName || '对方' }}</div>
<div class="rel__partner-hint">你们已互相喜欢,正在了解中</div>
</div>
</div>
</template>
<!-- 操作区 -->
<UiSectionHeader title="状态管理" />
<div class="rel__actions">
<template v-if="status === 'searching'">
<van-button type="primary" block round :loading="actionLoading" @click="onGettingToKnow">
设置为「正在了解」
</van-button>
<van-button plain block round :loading="actionLoading" @click="onPause">暂停寻找</van-button>
</template>
<template v-else-if="status === 'paused'">
<van-button type="primary" block round :loading="actionLoading" @click="onResume">恢复寻找</van-button>
</template>
<template v-else-if="status === 'getting_to_know'">
<van-button plain block round :loading="actionLoading" @click="onResume">恢复寻找</van-button>
</template>
<p v-else class="rel__tip">当前状态无需调整</p>
</div>
<p class="rel__free">本平台为公益服务,所有功能永久免费</p>
</template>
</div>
</PageLayout>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { showConfirmDialog } from 'vant'
import { profileService } from '@/api/services'
import { useAppStore } from '@/stores/app'
import PageLayout from '@/layouts/PageLayout.vue'
import UiLoading from '@/components/UiLoading.vue'
import UiErrorState from '@/components/UiErrorState.vue'
import UiSectionHeader from '@/components/UiSectionHeader.vue'
import UiAvatar from '@/components/UiAvatar.vue'
import UiTag from '@/components/UiTag.vue'
const appStore = useAppStore()
const STATUS_META: Record<string, { label: string; desc: string; kind: 'primary' | 'success' | 'warning' | 'danger' | 'info' }> = {
searching: {
label: '正在寻找',
desc: '你正在被推荐给合适的对象,也会收到新的喜欢提示。',
kind: 'success',
},
paused: {
label: '已暂停寻找',
desc: '你暂时不会被推荐给他人,也无法收到新的喜欢提示。已有聊天仍可继续使用。',
kind: 'warning',
},
getting_to_know: {
label: '正在了解',
desc: '你正在与某位对象互相了解中,平台会适当减少新推荐,让你专注这段关系。',
kind: 'info',
},
matched_successfully: {
label: '已成功牵手',
desc: '恭喜!你与对方已成功牵手,平台将不再为你推荐其他人。如有需要可联系公益红娘。',
kind: 'success',
},
service_exited: {
label: '已退出婚恋服务',
desc: '你已退出婚恋服务,不再出现在推荐和搜索结果中。历史匹配与聊天记录已保留。',
kind: 'info',
},
}
const loading = ref(true)
const error = ref(false)
const actionLoading = ref(false)
const status = ref('searching')
const recommendationEnabled = ref(true)
const matchingWith = ref<any>(null)
const meta = computed(() => STATUS_META[status.value] || STATUS_META.searching)
async function load() {
loading.value = true
error.value = false
try {
const data: any = await profileService.getRelationshipStatus()
status.value = data?.status || 'searching'
recommendationEnabled.value = data?.recommendationEnabled !== false
matchingWith.value = data?.matchingWith || null
} catch {
error.value = true
} finally {
loading.value = false
}
}
async function run(fn: () => Promise<any>) {
if (actionLoading.value) return
actionLoading.value = true
try {
await fn()
await load()
} catch (err: any) {
appStore.toastError(err?.message || '操作失败')
} finally {
actionLoading.value = false
}
}
async function onPause() {
try {
await showConfirmDialog({
title: '暂停寻找',
message: '暂停后你将不会被推荐给他人,已有聊天仍可继续使用。是否继续?',
})
} catch {
return
}
await run(async () => {
await profileService.pauseSearching()
appStore.toastSuccess('已暂停寻找')
})
}
async function onResume() {
await run(async () => {
await profileService.resumeSearching()
appStore.toastSuccess('已恢复寻找')
})
}
async function onGettingToKnow() {
try {
await showConfirmDialog({
title: '设置为「正在了解」',
message: '设置后平台会适当减少新推荐,让你专注这段关系。是否继续?',
})
} catch {
return
}
await run(async () => {
await profileService.markGettingToKnow()
appStore.toastSuccess('已设置为正在了解')
})
}
onMounted(load)
</script>
<style scoped>
.rel {
padding: 12px 16px 24px;
}
.rel__card {
background: var(--mini-surface);
border-radius: var(--mini-radius);
box-shadow: var(--mini-shadow);
padding: 16px;
}
.rel__card-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.rel__card-label {
font-size: 13px;
color: var(--mini-text-secondary);
}
.rel__card-desc {
margin: 10px 0 0;
font-size: 13px;
line-height: 1.7;
color: var(--mini-text);
}
.rel__partner {
display: flex;
align-items: center;
gap: 12px;
background: var(--mini-surface);
border-radius: var(--mini-radius);
box-shadow: var(--mini-shadow);
padding: 12px;
}
.rel__partner-main {
flex: 1;
min-width: 0;
}
.rel__partner-name {
font-size: 15px;
font-weight: 600;
color: var(--mini-text-strong);
}
.rel__partner-hint {
font-size: 12px;
color: var(--mini-text-secondary);
margin-top: 2px;
}
.rel__actions {
display: flex;
flex-direction: column;
gap: 10px;
}
.rel__tip {
text-align: center;
font-size: 13px;
color: var(--mini-text-muted);
margin: 0;
}
.rel__free {
text-align: center;
font-size: 11px;
color: var(--mini-text-muted);
margin: 20px 0 0;
}
</style>
@@ -0,0 +1,79 @@
<template>
<PageLayout title="设置">
<div class="settings">
<UiSectionHeader title="通知与消息" />
<van-cell-group inset>
<van-cell
title="通知中心"
value="系统通知、活动提醒"
is-link
@click="router.push('/notifications')"
/>
</van-cell-group>
<UiSectionHeader title="关系状态" />
<van-cell-group inset>
<van-cell
title="关系状态"
value="查看并切换当前婚恋状态"
is-link
@click="router.push('/settings/relationship')"
/>
</van-cell-group>
<UiSectionHeader title="安全与隐私" />
<van-cell-group inset>
<van-cell
title="隐私设置"
value="推荐可见、活动邀请、最近上线"
is-link
@click="router.push('/settings/privacy')"
/>
<van-cell title="黑名单管理" is-link @click="router.push('/settings/blacklist')" />
</van-cell-group>
<UiSectionHeader title="账号" />
<van-cell-group inset>
<van-cell title="账号状态" is-link @click="router.push('/account/status')" />
<van-cell title="帮助中心" is-link @click="router.push('/help')" />
<van-cell title="关于善缘丹阳" is-link @click="router.push('/about')" />
</van-cell-group>
<div class="settings__logout" @click="router.push('/account/logout')">退出登录</div>
<p class="settings__free">本平台为公益服务,所有功能永久免费</p>
</div>
</PageLayout>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import PageLayout from '@/layouts/PageLayout.vue'
import UiSectionHeader from '@/components/UiSectionHeader.vue'
const router = useRouter()
</script>
<style scoped>
.settings {
padding: 8px 0 24px;
}
.settings :deep(.van-cell-group--inset) {
margin: 0 16px 8px;
}
.settings__logout {
margin: 16px;
text-align: center;
color: var(--mini-danger);
background: var(--mini-surface);
border-radius: var(--mini-radius);
padding: 13px;
font-size: 14px;
box-shadow: var(--mini-shadow);
}
.settings__free {
text-align: center;
font-size: 11px;
color: var(--mini-text-muted);
margin: 12px 0 0;
}
</style>