Files
QSY-lvjuxcx/pkgAccount/registration_records/registration_records.vue
2026-06-29 09:42:53 +08:00

481 lines
13 KiB
Vue

<template>
<view class="registration-records-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view>
</tn-nav-bar>
<view class="page-main tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }">
<scroll-view class="filter-scroll" scroll-x :show-scrollbar="false">
<view class="filter-row tn-margin-bottom-lg">
<view
v-for="item in statusOptions"
:key="item.value"
class="filter-chip"
:class="{ 'filter-chip--active': approvalStatus === item.value }"
@click="changeStatus(item.value)"
>
{{ item.label }}
</view>
</view>
</scroll-view>
<view v-if="providerUnavailable" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('providerUnavailableTitle') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('providerUnavailableDesc') }}</view>
</view>
<view v-else-if="list.length">
<view
v-for="item in list"
:key="getRecordKey(item)"
class="record-card tn-margin-bottom-lg"
@click="goToRecordDetail(item)"
>
<view class="card-head tn-flex tn-flex-row-between tn-flex-col-center">
<view class="card-title-wrap">
<view class="card-title">{{ item.name || item.account || t('registrationFallbackTitle') }}</view>
<view class="card-code tn-margin-top-xs">{{ item.code || '--' }}</view>
</view>
<view :class="['status-badge', 'status-' + resolveStatusType(item.approvalStatus)]">
{{ getDisplayStatusText(item) }}
</view>
</view>
<view class="card-body tn-margin-top">
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('accountLabel') }}</text>
<text class="info-value">{{ item.account || '--' }}</text>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('phoneLabel') }}</text>
<text class="info-value">{{ item.phone || '--' }}</text>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('idNumberLabel') }}</text>
<text class="info-value">{{ item.idCard || item.passportNumber || '--' }}</text>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('submittedAtLabel') }}</text>
<text class="info-value">{{ formatDateTime(item.createTime) }}</text>
</view>
<view v-if="item.approveTime" class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('approvedAtLabel') }}</text>
<text class="info-value">{{ formatDateTime(item.approveTime) }}</text>
</view>
<view v-if="item.approveReason" class="remark-panel tn-margin-top">
<view class="remark-label">{{ item.approvalStatus === -1 ? t('rejectReasonLabel') : t('approvalRemarkLabel') }}</view>
<view class="remark-text tn-margin-top-xs">{{ item.approveReason }}</view>
</view>
</view>
</view>
<view class="load-state">
<text v-if="loadingMore">{{ t('loadingMore') }}</text>
<text v-else-if="!hasMore">{{ t('noMore') }}</text>
<text v-else>{{ t('pullUpMore') }}</text>
</view>
</view>
<view v-else-if="!loading" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('emptyTitle') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('emptyDesc') }}</view>
<view class="action-btn tn-margin-top-lg" @click="goToRegister">
<text class="tn-text-bold">{{ t('goRegister') }}</text>
</view>
</view>
<view v-if="loading && !list.length && !providerUnavailable" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('loadingTitle') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('loadingDesc') }}</view>
</view>
</view>
</view>
</template>
<script>
import { registrationApplicationMePage } from '@/api/index.js'
function formatPageResponse(response) {
const payload = response && response.data ? response.data : response
return payload && typeof payload === 'object' ? payload : {}
}
function normalizeRecordList(list = []) {
if (!Array.isArray(list)) {
return []
}
return list.filter(item => item && typeof item === 'object')
}
export default {
data() {
return {
providerValue: '',
providerUnavailable: false,
approvalStatus: '',
pageIndex: 1,
pageSize: 10,
total: 0,
list: [],
loading: false,
loadingMore: false,
hasMore: true,
statusOptions: []
}
},
created() {
this.syncStatusOptions()
this.syncNavigationTitle()
},
onShow() {
this.syncNavigationTitle()
this.reloadList()
},
onPullDownRefresh() {
this.reloadList().finally(() => {
uni.stopPullDownRefresh()
})
},
onReachBottom() {
this.loadMore()
},
computed: {
language() {
return this.$getLanguage()
}
},
watch: {
language() {
this.syncNavigationTitle()
this.syncStatusOptions()
this.reloadList()
}
},
methods: {
t(key, params) {
return this.$tm('pkgAccount.registration_records', key, params)
},
syncStatusOptions() {
this.statusOptions = [
{ label: this.t('statusAll'), value: '' },
{ label: this.t('statusPending'), value: 0 },
{ label: this.t('statusApproved'), value: 1 },
{ label: this.t('statusRejected'), value: -1 }
]
},
syncNavigationTitle() {
this.$syncNavigationTitle()
},
getWechatCode() {
return new Promise((resolve) => {
// #ifdef MP-WEIXIN
uni.login({
provider: 'weixin',
success: (res) => {
resolve(res.code || '')
},
fail: (err) => {
console.error('getWechatCode failed', err)
resolve('')
}
})
// #endif
// #ifndef MP-WEIXIN
resolve('')
// #endif
})
},
getStatusText(status) {
const map = {
0: this.t('statusPending'),
1: this.t('statusApproved'),
'-1': this.t('statusRejected')
}
return map[status] || this.t('statusProcessing')
},
getDisplayStatusText(item = {}) {
if (this.language !== 'en' && item.approvalStatusDesc) {
return item.approvalStatusDesc
}
return this.getStatusText(item.approvalStatus)
},
resolveStatusType(status) {
if (Number(status) === 1) {
return 'approved'
}
if (Number(status) === -1) {
return 'rejected'
}
return 'pending'
},
formatDateTime(value) {
if (!value) {
return '--'
}
const text = String(value).replace('T', ' ')
return text.length > 16 ? text.slice(0, 16) : text
},
getRecordKey(item = {}) {
return item.code || `${item.account || 'registration'}-${item.createTime || ''}`
},
buildRecordRoutePayload(item = {}) {
return {
code: item.code || '',
account: item.account || '',
name: item.name || '',
gender: typeof item.gender === 'number' ? item.gender : null,
genderDesc: item.genderDesc || '',
email: item.email || '',
phone: item.phone || '',
country: item.country || '',
idCard: item.idCard || '',
workUnit: item.workUnit || '',
passportNumber: item.passportNumber || '',
school: item.school || '',
hobbyTags: item.hobbyTags || [],
approvalStatus: typeof item.approvalStatus === 'number' ? item.approvalStatus : undefined,
approvalStatusDesc: item.approvalStatusDesc || '',
approveReason: item.approveReason || '',
approveTime: item.approveTime || '',
createTime: item.createTime || ''
}
},
async ensureProviderValue() {
const providerValue = await this.getWechatCode()
this.providerValue = providerValue || ''
this.providerUnavailable = !this.providerValue
return this.providerValue
},
async reloadList() {
this.pageIndex = 1
this.hasMore = true
this.list = []
const providerValue = await this.ensureProviderValue()
if (!providerValue) {
this.total = 0
return
}
await this.fetchList(true)
},
async loadMore() {
if (!this.hasMore || this.loading || this.loadingMore || this.providerUnavailable) {
return
}
this.pageIndex += 1
await this.fetchList(false)
},
async fetchList(isReload = false) {
if (!this.providerValue) {
return
}
if (isReload) {
this.loading = true
} else {
this.loadingMore = true
}
const params = {
ProviderValue: this.providerValue,
PageIndex: this.pageIndex,
PageSize: this.pageSize
}
if (this.approvalStatus !== '' && this.approvalStatus !== null && typeof this.approvalStatus !== 'undefined') {
params.ApprovalStatus = this.approvalStatus
}
await registrationApplicationMePage(params, this.language)
.then((response) => {
const pageData = formatPageResponse(response)
const nextList = normalizeRecordList(pageData.data || [])
const total = Number(pageData.total || 0)
this.total = total
this.list = isReload ? nextList : this.list.concat(nextList)
this.hasMore = this.list.length < total && nextList.length > 0
})
.catch((error) => {
if (!isReload) {
this.pageIndex = Math.max(1, this.pageIndex - 1)
}
uni.showToast({
title: (error && (error.message || error.msg)) || this.t('loadError'),
icon: 'none'
})
})
.finally(() => {
this.loading = false
this.loadingMore = false
})
},
changeStatus(value) {
if (this.approvalStatus === value) {
return
}
this.approvalStatus = value
this.reloadList()
},
goToRecordDetail(item = {}) {
const code = item.code || ''
const payload = this.buildRecordRoutePayload(item)
uni.navigateTo({
url: `/pkgAccount/auth_result/auth_result?from=registration-records&code=${encodeURIComponent(code)}&payload=${encodeURIComponent(JSON.stringify(payload))}`
})
},
goToRegister() {
uni.navigateTo({
url: '/pkgAccount/register/register'
})
}
}
}
</script>
<style scoped lang="scss">
.registration-records-page {
min-height: 100vh;
background: linear-gradient(180deg, #F0F6FF 0%, #F8FAFF 100%);
}
.record-card,
.empty-panel {
background: #FFFFFF;
border-radius: 24rpx;
padding: 32rpx;
box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.04);
border: 1px solid rgba($brand-primary, 0.05);
}
.record-card:active {
transform: scale(0.99);
}
.empty-title,
.card-title {
font-size: 32rpx;
font-weight: 700;
color: #1A2233;
}
.empty-desc,
.card-code,
.info-label,
.remark-label,
.load-state {
color: #6A7A99;
}
.filter-scroll {
width: 100%;
white-space: nowrap;
}
.filter-row {
display: inline-flex;
flex-wrap: nowrap;
gap: 16rpx;
padding: 4rpx;
}
.filter-chip {
flex-shrink: 0;
padding: 14rpx 28rpx;
border-radius: 999rpx;
background: #FFFFFF;
border: 1rpx solid rgba($brand-primary, 0.15);
color: #6A7A99;
font-size: 24rpx;
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.05);
transition: all 0.2s;
}
.filter-chip--active {
background: #FFFFFF;
border-color: $brand-primary;
color: $brand-primary;
font-weight: bold;
box-shadow: 0 4rpx 16rpx rgba($brand-primary, 0.1);
}
.card-code {
font-size: 22rpx;
}
.status-badge {
padding: 8rpx 20rpx;
border-radius: 999rpx;
font-size: 22rpx;
font-weight: 700;
}
.status-pending {
background: rgba(245, 158, 11, 0.12);
color: #B45309;
}
.status-approved {
background: rgba($brand-primary, 0.12);
color: $brand-primary;
}
.status-rejected {
background: rgba(239, 68, 68, 0.12);
color: #B91C1C;
}
.info-row {
padding: 16rpx 0;
border-bottom: 1px dashed rgba($brand-primary, 0.15);
}
.info-row:last-child {
border-bottom: none;
}
.info-value {
max-width: 62%;
text-align: right;
color: #1A2233;
font-weight: 500;
}
.remark-panel {
padding: 24rpx;
border-radius: 20rpx;
background: rgba($brand-primary, 0.02);
border: 1rpx solid rgba($brand-primary, 0.1);
}
.remark-text {
color: #1A2233;
line-height: 1.6;
}
.action-btn {
width: 100%;
height: 96rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 999rpx;
background: linear-gradient(135deg, rgba($brand-primary, 0.9) 0%, $brand-primary 100%);
color: #FFFFFF;
box-shadow: 0 12rpx 32rpx rgba($brand-primary, 0.25);
}
.load-state {
text-align: center;
font-size: 24rpx;
padding: 12rpx 0 24rpx;
}
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
</style>