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
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:
@@ -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>
|
||||
Reference in New Issue
Block a user