Files
jiapuapp/pages/genealogy/g10-application-review.vue
T
2026-07-17 08:08:33 +08:00

167 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 页面编号G-10用途管理员入谱申请审核与空/失败状态 -->
<template>
<view class="review-page">
<GenealogyPageBackground />
<view class="review-page__header"><PageHeader title="入谱审核" action="说明" @action="showHelp" /></view>
<view class="review-content" :class="{
'review-state--list': reviewState === 'list',
'review-state--empty': reviewState === 'empty',
'review-state--error': reviewState === 'error',
'review-state--no-permission': reviewState === 'no-permission'
}">
<view class="review-intro">
<text>核实亲属关系后再决定</text>
<text>审核结果会通过消息告知申请人</text>
<image src="/static/assets/modules/genealogy/transparent/section-divider.png" mode="scaleToFill" />
</view>
<template v-if="reviewState === 'list'">
<view v-for="item in applications" :key="item.id" class="application-card">
<image class="application-card__skin" src="/static/assets/modules/genealogy/opaque/application-record-card.png" mode="scaleToFill" />
<view class="application-card__body">
<text class="application-card__name">{{ item.name }}</text>
<text class="application-card__phone">{{ item.phone }}</text>
<text class="application-card__time">{{ item.appliedAt }}</text>
<text class="application-card__relation">{{ item.relation }}</text>
<view v-if="item.status === 'PENDING'" class="review-actions">
<view class="review-action" @click="confirmAudit(item, false)">
<image src="/static/assets/foundation/transparent/a01-scroll-secondary-v3.png" mode="aspectFit" /><text>拒绝</text>
</view>
<view class="review-action" @click="confirmAudit(item, true)">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" /><text>通过</text>
</view>
</view>
<text v-else class="application-card__status">{{ item.status === 'APPROVED' ? '已通过' : '已拒绝' }}</text>
</view>
</view>
</template>
<view v-else class="review-state-card">
<image src="/static/assets/modules/genealogy/opaque/application-status-card.png" mode="scaleToFill" />
<view class="review-state-card__copy">
<text>{{ stateTitle }}</text>
<text>{{ stateCopy }}</text>
</view>
</view>
<view v-if="reviewState === 'error'" class="review-page__retry" @click="loadApplications({ genealogyId })">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="aspectFit" /><text>重新查看</text>
</view>
</view>
<AppDialog
:visible="!!confirmation || helpVisible"
:title="helpVisible ? '审核说明' : confirmation?.approved ? '确认通过申请?' : '确认拒绝申请?'"
:message="helpVisible ? '请核对申请人的姓名、亲属关系和补充说明,仅确认与本家谱存在真实关系的申请。' : confirmation?.approved ? '通过后,申请人将成为本家谱成员。' : '拒绝后,申请人会收到审核结果,并可修改后重新申请。'"
:confirm-text="helpVisible ? '我知道了' : confirmation?.approved ? '确认通过' : '确认拒绝'"
:show-cancel="!!confirmation"
@confirm="helpVisible ? closeDialog() : applyAudit()"
@cancel="closeDialog"
@close="closeDialog"
/>
<view v-if="feedbackVisible" class="review-feedback">
<image src="/static/assets/foundation/transparent/a01-scroll-toast-v3.png" mode="scaleToFill" />
<text>{{ feedbackMessage }}</text>
</view>
</view>
</template>
<script setup>
import { computed, ref } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app'
import AppDialog from '@/components/AppDialog.vue'
import GenealogyPageBackground from '@/components/GenealogyPageBackground.vue'
import PageHeader from '@/components/PageHeader.vue'
import { genealogyContext } from '@/utils/genealogy-context.js'
const applications = ref([])
const reviewSamples = [
{ id: 1, name: '汤志成', phone: '139****6421', relation: '自述为汤正华堂侄 · 祖居洛阳', appliedAt: '今天 10:24', status: 'PENDING' },
{ id: 2, name: '汤雨薇', phone: '136****2798', relation: '自述为汤正国之女 · 已补充长辈姓名', appliedAt: '昨天 18:02', status: 'PENDING' }
]
const genealogyId = ref('')
const reviewState = ref('loading')
const errorMessage = ref('')
const confirmation = ref(null)
const helpVisible = ref(false)
const feedbackVisible = ref(false)
const feedbackMessage = ref('')
let feedbackTimer = null
onUnload(() => { if (feedbackTimer) clearTimeout(feedbackTimer) })
const stateTitle = computed(() => reviewState.value === 'empty' ? '暂无待审核申请' : reviewState.value === 'no-permission' ? '当前账号无审核权限' : '审核列表暂时无法读取')
const stateCopy = computed(() => reviewState.value === 'empty' ? '新的入谱申请会在这里出现,并同步发送消息提醒。' : reviewState.value === 'no-permission' ? '只有家谱所有者或具备审核权限的管理员可以处理申请。' : errorMessage.value || '请检查网络或家谱权限后重试。')
const loadApplications = (query = {}) => {
reviewState.value = 'loading'
errorMessage.value = ''
genealogyId.value = query.genealogyId?.value || query.genealogyId || genealogyId.value || genealogyContext.getCurrentGenealogyId()
if (query.state === 'empty') { reviewState.value = 'empty'; return }
if (query.state === 'error') { reviewState.value = 'error'; return }
if (query.state === 'no-permission') { reviewState.value = 'no-permission'; return }
if (!genealogyId.value) {
errorMessage.value = '没有找到当前家谱,请从家谱总览进入。'
reviewState.value = 'error'
return
}
genealogyContext.setCurrentGenealogyId(genealogyId.value)
applications.value = reviewSamples.map((item) => ({ ...item }))
reviewState.value = 'list'
}
onLoad(loadApplications)
const confirmAudit = (item, approved) => {
confirmation.value = { item, approved }
}
const closeDialog = () => { confirmation.value = null; helpVisible.value = false }
const showFeedback = (message) => {
feedbackMessage.value = message
feedbackVisible.value = true
if (feedbackTimer) clearTimeout(feedbackTimer)
feedbackTimer = setTimeout(() => { feedbackVisible.value = false; feedbackTimer = null }, 1800)
}
const applyAudit = () => {
const current = confirmation.value
if (!current) return
current.item.status = current.approved ? 'APPROVED' : 'REJECTED'
closeDialog()
showFeedback(current.approved ? '已通过申请' : '已拒绝申请')
}
const showHelp = () => { helpVisible.value = true }
</script>
<style scoped lang="scss">
.review-page { position: relative; min-height: 100vh; overflow-x: hidden; background: $paper; }
.review-page__header { position: relative; z-index: 3; }
.review-content { position: relative; z-index: 2; padding: 24rpx 24rpx 60rpx; }
.review-intro { position: relative; margin: 0 8rpx 24rpx; padding-bottom: 22rpx; }
.review-intro text { display: block; }
.review-intro text:first-child { color: $ink; font-family: 'STKaiti', 'KaiTi', serif; font-size: 29rpx; font-weight: 700; }
.review-intro text:nth-child(2) { margin-top: 8rpx; color: $ink-muted; font-size: 21rpx; }
.review-intro image { position: absolute; right: 0; bottom: 0; left: 0; width: 100%; height: 14rpx; }
.application-card { position: relative; width: 100%; height: calc((100vw - 24px) * .34286); min-height: 228rpx; max-height: 282rpx; margin-bottom: 18rpx; }
.application-card__skin { position: absolute; inset: 0; width: 100%; height: 100%; }
.application-card__body { position: absolute; inset: 18% 7% 12% 8.5%; }
.application-card__name { color: $ink; font-family: 'STKaiti', 'KaiTi', serif; font-size: 30rpx; font-weight: 700; }
.application-card__phone { margin-left: 14rpx; color: $ink-muted; font-size: 20rpx; }
.application-card__time { position: absolute; top: 3rpx; right: 0; color: #998873; font-size: 20rpx; }
.application-card__relation { display: block; max-width: 58%; margin-top: 15rpx; color: $ink-muted; font-size: 22rpx; }
.review-actions { position: absolute; right: 0; bottom: 0; display: flex; gap: 12rpx; }
.review-action { position: relative; width: 128rpx; height: 60rpx; }
.review-action image { position: absolute; inset: 0; width: 100%; height: 100%; }
.review-action text { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; height: 100%; color: $ink; font-size: 22rpx; font-weight: 700; }
.review-action:last-child text { color: #fff9ed; }
.application-card__status { position: absolute; right: 3%; bottom: 10%; color: $brand-red; font-size: 24rpx; font-weight: 700; }
.review-state-card { position: relative; width: 100%; height: calc((100vw - 24px) * .34286); min-height: 228rpx; margin-top: 70rpx; }
.review-state-card > image { position: absolute; inset: 0; width: 100%; height: 100%; }
.review-state-card__copy { position: absolute; inset: 22% 12%; display: flex; flex-direction: column; justify-content: center; text-align: center; }
.review-state-card__copy text:first-child { color: $ink; font-family: 'STKaiti', 'KaiTi', serif; font-size: 31rpx; font-weight: 700; }
.review-state-card__copy text:last-child { margin-top: 15rpx; color: $ink-muted; font-size: 22rpx; line-height: 1.6; }
.review-page__retry { position: relative; width: 420rpx; height: 82rpx; margin: 32rpx auto 0; }
.review-page__retry image { position: absolute; inset: 0; width: 100%; height: 100%; }
.review-page__retry text { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; height: 100%; color: #fff9ed; font-size: 26rpx; font-weight: 700; }
.review-feedback { position: fixed; z-index: 40; top: 140rpx; left: 50%; display: flex; min-width: 250rpx; min-height: 76rpx; align-items: center; justify-content: center; padding: 0 36rpx; transform: translateX(-50%); }
.review-feedback image { position: absolute; inset: 0; width: 100%; height: 100%; }
.review-feedback text { position: relative; z-index: 1; color: $ink; font-size: 23rpx; }
</style>