Files
jiapuapp/pages/genealogy/capability-center.vue
T

115 lines
7.1 KiB
Vue

<template>
<view class="capability-page">
<GenealogyPageBackground />
<view class="page-header"><PageHeader title="资料与回收站" custom-back @back="goBack" /></view>
<view class="page-content">
<view class="tab-row">
<button :class="['tab', { active: tab === 'completeness' }]" @click="tab = 'completeness'">资料完整度</button>
<button :class="['tab', { active: tab === 'recycle' }]" @click="tab = 'recycle'">内容回收站</button>
</view>
<view v-if="loading" class="state-card"><AppLoading text="正在读取家谱资料" /></view>
<view v-else-if="error" class="state-card"><text>{{ error }}</text><AppButton block label="重新加载" @click="load" /></view>
<view v-else-if="tab === 'completeness' && !hasValidCompleteness" class="state-card"><text>{{ completenessError || '资料完整度暂时无法读取。' }}</text><AppButton block label="重新加载" @click="load" /></view>
<view v-else-if="tab === 'completeness'" class="panel">
<text class="panel-title">资料完成度 {{ completeness?.completionRate || 0 }}%</text>
<text class="panel-copy">已完成 {{ completeness?.completedCount || 0 }} / {{ completeness?.totalCount || 0 }} </text>
<view v-if="!completeness?.missingItems?.length" class="empty-copy">这部家谱的基础资料已补充完整</view>
<view v-for="item in completeness?.missingItems || []" :key="item.code" class="row"><text>{{ item.displayText }}</text><text>待补充</text></view>
</view>
<view v-else-if="recycleError" class="state-card"><text>{{ recycleError }}</text><AppButton block label="重新加载" @click="load" /></view>
<view v-else class="panel">
<text class="panel-title">已删除内容</text>
<text class="panel-copy">可恢复的内容会显示恢复按钮</text>
<view v-if="!recycle.rows.length" class="empty-copy">回收站暂时没有内容</view>
<view v-for="item in recycle.rows" :key="`${item.resourceType}-${item.resourceId}`" class="row row--recycle">
<view><text>{{ item.resourceTitle || '未命名内容' }}</text><text class="row-meta">{{ item.resourceType }} · {{ item.deletedAt || '删除时间未知' }}</text></view>
<AppButton v-if="item.canRestore" class="restore-button" compact type="secondary" :disabled="restoringKey === `${item.resourceType}-${item.resourceId}`" label="恢复" @click="restore(item)" />
<text v-else class="row-meta">{{ recycleDisabledReason(item.disabledReason) }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app'
import AppButton from '@/components/AppButton.vue'
import AppLoading from '@/components/AppLoading.vue'
import PageHeader from '@/components/PageHeader.vue'
import GenealogyPageBackground from '@/components/genealogy/PageBackground.vue'
import { genealogyCapabilityApi } from '@/services/api/genealogy-capability-service.js'
import { createRequestController, isRequestCancelled } from '@/services/api/request-controller.js'
import { getRequestErrorMessage } from '@/services/api/request-error-message.js'
import { goBack } from '@/utils/navigation/gateway.js'
const genealogyId = ref('')
const tab = ref('completeness')
const loading = ref(true)
const error = ref('')
const completenessError = ref('')
const recycleError = ref('')
const completeness = ref(null)
const recycle = ref({ rows: [], total: 0 })
const restoringKey = ref('')
const controller = createRequestController()
const recycleDisabledReason = (reason) => reason === 'ALREADY_RESTORED' ? '已恢复' : '不可恢复'
const hasValidCompleteness = computed(() => (
Number.isSafeInteger(completeness.value?.totalCount) &&
completeness.value.totalCount > 0 &&
Number.isSafeInteger(completeness.value?.completedCount) &&
completeness.value.completedCount >= 0 &&
completeness.value.completedCount <= completeness.value.totalCount &&
Number.isSafeInteger(completeness.value?.completionRate) &&
completeness.value.completionRate >= 0 &&
completeness.value.completionRate <= 100 &&
Array.isArray(completeness.value?.missingItems)
))
const load = async () => {
if (!/^[1-9]\d*$/.test(genealogyId.value)) { error.value = '缺少有效家谱信息'; loading.value = false; return }
controller.abort(); loading.value = true; error.value = ''; completenessError.value = ''; recycleError.value = ''
const [completenessResult, recycleResult] = await Promise.allSettled([
genealogyCapabilityApi.getCompleteness(genealogyId.value, { requestController: controller }),
genealogyCapabilityApi.getRecyclePage(genealogyId.value, { pageNum: 1, pageSize: 50, recycleStatus: 'OPEN' }, { requestController: controller })
])
if (completenessResult.status === 'fulfilled') completeness.value = completenessResult.value
else if (!isRequestCancelled(completenessResult.reason)) {
completeness.value = null
completenessError.value = getRequestErrorMessage(completenessResult.reason, '资料完整度暂时无法读取。')
}
if (recycleResult.status === 'fulfilled') recycle.value = recycleResult.value
else if (!isRequestCancelled(recycleResult.reason)) {
recycle.value = { rows: [], total: 0 }
recycleError.value = getRequestErrorMessage(recycleResult.reason, '回收站暂时无法读取。')
}
loading.value = false
}
const restore = async (item) => {
const key = `${item.resourceType}-${item.resourceId}`
if (restoringKey.value) return
restoringKey.value = key
try { await genealogyCapabilityApi.restoreRecycleItem(genealogyId.value, item.resourceType, item.resourceId); await load() }
catch (requestError) { recycleError.value = getRequestErrorMessage(requestError, '恢复失败,请稍后重试。') }
finally { restoringKey.value = '' }
}
onLoad((query) => { genealogyId.value = String(query?.genealogyId || ''); void load() })
onUnload(() => controller.abort())
</script>
<style lang="scss" scoped>
.capability-page { min-height: 100vh; color: $ink; }
.page-header { position: relative; z-index: 1; }
.page-content { position: relative; z-index: 1; padding: 24rpx 28rpx calc(72rpx + env(safe-area-inset-bottom)); }
.tab-row { display: flex; gap: 16rpx; margin-bottom: 20rpx; }
.tab { flex: 1; min-height: 80rpx; border: 1rpx solid rgba($gold, .38); border-radius: 12rpx; background: rgba(255,252,245,.88); color: $ink-muted; font-size: 26rpx; }
.tab.active { border-color: $brand-red; background: $brand-red; color: #fff; }
.panel,.state-card { padding: 28rpx; border: 1rpx solid rgba($gold, .38); border-radius: 16rpx; background: rgba(255,252,245,.92); }
.panel-title,.panel-copy,.row text,.empty-copy { display: block; }
.panel-title { font-size: 32rpx; font-weight: 700; }.panel-copy,.row-meta,.empty-copy { margin-top: 12rpx; color: $ink-muted; font-size: 25rpx; }
.row { display:flex; justify-content:space-between; align-items:center; gap:16rpx; padding:24rpx 0; border-top:1rpx solid rgba($gold, .38); }.row:first-of-type { margin-top:20rpx; }.row--recycle > view { flex:1; min-width:0; }
.restore-button { flex: 0 0 176rpx; width: 176rpx; }
</style>