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

630 lines
20 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.

<template>
<view class="auth-result-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-content tn-padding" :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view class="status-card fade-in">
<!-- 状态大图标 -->
<view class="status-icon-wrapper tn-flex tn-flex-row-center tn-flex-col-center" :class="authStatus">
<text v-if="authStatus === 'approved'" class="tn-icon-success-circle-fill status-icon icon-success"></text>
<text v-else-if="authStatus === 'rejected'" class="tn-icon-close-circle-fill status-icon icon-rejected"></text>
<text v-else-if="authStatus === 'pending'" class="tn-icon-time-fill status-icon icon-pending"></text>
<text v-else class="tn-icon-warning-fill status-icon icon-unsubmitted"></text>
</view>
<!-- 状态标题与描述 -->
<view class="tn-text-center tn-margin-top-lg tn-padding-left-lg tn-padding-right-lg">
<view class="card-title">{{ titleText }}</view>
<!-- <view class="card-desc tn-margin-top-sm">{{ descText }}</view> -->
</view>
<!-- <view v-if="applicationCode" class="code-chip tn-margin-top-lg">
申请编号:{{ applicationCode }}
</view> -->
<!-- 审批备注 -->
<view v-if="approvalRemark" class="reason-box tn-margin-top-xl tn-margin-left-lg tn-margin-right-lg" :class="authStatus">
<view class="reason-title tn-flex tn-flex-row-center tn-flex-col-center">
<text :class="remarkIconClass" class="tn-margin-right-xs"></text>{{ approvalRemarkTitle }}
</view>
<view class="reason-content tn-margin-top-sm tn-text-center">{{ approvalRemark }}</view>
</view>
<!-- 提交信息摘要 -->
<view v-if="profileSummary.length" class="summary-box tn-margin-top-xl tn-margin-bottom-lg tn-margin-left-lg tn-margin-right-lg">
<view class="summary-title tn-margin-bottom-sm">{{ t('submittedInfoTitle') }}</view>
<view v-for="item in profileSummary" :key="item.label" class="summary-row tn-flex tn-flex-row-between tn-padding-top-sm tn-padding-bottom-sm">
<text class="summary-label">{{ item.label }}</text>
<text class="summary-value tn-text-ellipsis">{{ item.value }}</text>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="action-group tn-margin-top-xl fade-in-up">
<view v-if="authStatus === 'approved'" class="custom-btn btn-primary" @click="goToLogin">
<text class="tn-text-bold">{{ t('goLogin') }}</text>
</view>
<view v-else-if="authStatus === 'rejected'" class="custom-btn btn-primary" @click="resubmit">
<text class="tn-text-bold">{{ t('resubmitRegister') }}</text>
</view>
<view v-else class="custom-btn btn-dark" @click="refreshStatus">
<text class="tn-text-bold tn-flex tn-flex-row-center tn-flex-col-center">
<text v-if="loading" class="tn-icon-loading tn-margin-right-xs spin"></text>
{{ loading ? t('refreshing') : t('refreshStatus') }}
</text>
</view>
<view class="custom-btn btn-light tn-margin-top" @click="goToRegistrationRecords">
<text class="tn-text-bold">{{ t('viewRegistrationRecords') }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
import { saveRegisteredUser } from '../../utils/app-state'
import { registrationApplicationMePage, registrationMyorder } from '@/api/index.js'
import { fetchEnums, getApprovalStatusLabel } from '../../utils/enum-store'
import { buildUserFromRegistrationApplication, mapRegistrationApprovalToAuthStatus, resolveRegistrationApplicationCode } from '../../utils/flow'
function formatPageResponse(response) {
const payload = response && response.data ? response.data : response
return payload && typeof payload === 'object' ? payload : {}
}
export default {
data() {
return {
loading: false,
applicationInfo: null,
targetCode: '',
sourceType: '',
i18n: {
zh: {
navTitle: '注册申请结果',
submittedInfoTitle: '已提交信息',
goLogin: '去登录',
resubmitRegister: '重新提交注册申请',
refreshing: '刷新中...',
refreshStatus: '刷新审批状态',
viewRegistrationRecords: '查看注册记录',
unsubmitted: '未提交',
titleUnsubmitted: '您还未提交注册申请',
titlePending: '注册申请已提交',
titleApproved: '注册审批已通过',
titleRejected: '注册审批未通过',
descUnsubmitted: '请先提交注册申请单,审批通过后才算注册成功。',
descPendingRecords: '当前展示的是该申请单的审批状态,可下拉或点击按钮刷新最新结果。',
descPendingDefault: '当前申请正在审批中,审批通过前无法登录。',
descApproved: '您的注册申请已审批通过,现在可以前往登录。',
descRejectedRecords: '当前展示的是该申请单的审批结果,请根据驳回原因修改信息后重新提交注册申请。',
descRejectedDefault: '请根据驳回原因修改信息后重新提交注册申请。',
remarkApproved: '通过备注',
remarkRejected: '驳回原因',
remarkPending: '审批备注',
roleForeign: '外籍人员',
roleDomestic: '中国籍',
labelIdentityType: '人员类型',
labelName: '姓名',
labelAccount: '注册账号',
labelCertificateNo: '证件号',
fallbackNone: '-',
latestDetailMissing: '未查到最新详情,已展示列表记录',
approvedPleaseLogin: '审批已通过,请登录',
fetchStatusFailed: '获取审批状态失败',
statusPending: '审核中',
statusApproved: '已通过',
statusRejected: '已驳回'
},
en: {
navTitle: 'Registration Result',
submittedInfoTitle: 'Submitted Information',
goLogin: 'Sign In',
resubmitRegister: 'Submit Registration Again',
refreshing: 'Refreshing...',
refreshStatus: 'Refresh Status',
viewRegistrationRecords: 'View Registration Records',
unsubmitted: 'Not Submitted',
titleUnsubmitted: 'No registration application submitted yet',
titlePending: 'Registration submitted',
titleApproved: 'Registration approved',
titleRejected: 'Registration was not approved',
descUnsubmitted: 'Please submit a registration application first. Registration is completed only after approval.',
descPendingRecords: 'This page shows the approval status of the selected application. Pull down or tap the button to refresh.',
descPendingDefault: 'Your application is under review and sign-in is unavailable before approval.',
descApproved: 'Your registration is approved and you can sign in now.',
descRejectedRecords: 'This page shows the approval result of the selected application. Please update the information and resubmit based on the rejection reason.',
descRejectedDefault: 'Please update the information and resubmit based on the rejection reason.',
remarkApproved: 'Approval Note',
remarkRejected: 'Rejection Reason',
remarkPending: 'Approval Note',
roleForeign: 'Foreign',
roleDomestic: 'Chinese',
labelIdentityType: 'Identity Type',
labelName: 'Name',
labelAccount: 'Account',
labelCertificateNo: 'Certificate No.',
fallbackNone: '-',
latestDetailMissing: 'Latest details are unavailable. Showing the list record instead.',
approvedPleaseLogin: 'Approved. Please sign in.',
fetchStatusFailed: 'Failed to fetch approval status',
statusPending: 'Under Review',
statusApproved: 'Approved',
statusRejected: 'Rejected'
}
}
}
},
computed: {
language() {
return this.$getLanguage()
},
authStatus() {
const status = this.applicationInfo ? this.applicationInfo.approvalStatus : undefined
return mapRegistrationApprovalToAuthStatus(status)
},
applicationCode() {
return this.targetCode || resolveRegistrationApplicationCode(this.applicationInfo) || ''
},
approvalStatusText() {
const status = this.applicationInfo ? this.applicationInfo.approvalStatus : undefined
if (typeof status === 'number') {
return this.getApprovalStatusLabelLocal(status)
}
return this.t('unsubmitted')
},
titleText() {
const map = {
unsubmitted: this.t('titleUnsubmitted'),
pending: this.t('titlePending'),
approved: this.t('titleApproved'),
rejected: this.t('titleRejected')
}
return map[this.authStatus] || map.unsubmitted
},
descText() {
const map = {
unsubmitted: this.t('descUnsubmitted'),
pending: this.sourceType === 'registration-records'
? this.t('descPendingRecords')
: this.t('descPendingDefault'),
approved: this.t('descApproved'),
rejected: this.sourceType === 'registration-records'
? this.t('descRejectedRecords')
: this.t('descRejectedDefault')
}
return map[this.authStatus] || map.unsubmitted
},
approvalRemarkTitle() {
const map = {
approved: this.t('remarkApproved'),
rejected: this.t('remarkRejected'),
pending: this.t('remarkPending'),
unsubmitted: this.t('remarkPending')
}
return map[this.authStatus] || map.pending
},
approvalRemark() {
return this.applicationInfo && this.applicationInfo.approveReason
? this.applicationInfo.approveReason
: ''
},
remarkIconClass() {
const map = {
approved: 'tn-icon-success',
rejected: 'tn-icon-warning',
pending: 'tn-icon-time',
unsubmitted: 'tn-icon-warning'
}
return map[this.authStatus] || map.pending
},
profileSummary() {
const profile = this.applicationInfo || {}
const identityType = profile.passportNumber ? 'foreign' : 'domestic'
const roleText = identityType === 'foreign' ? this.t('roleForeign') : identityType === 'domestic' ? this.t('roleDomestic') : this.t('fallbackNone')
const certificateNo = profile.passportNumber || profile.idCard || this.t('fallbackNone')
return [
{ label: this.t('labelIdentityType'), value: roleText },
{ label: this.t('labelName'), value: profile.name || this.t('fallbackNone') },
{ label: this.t('labelAccount'), value: profile.account || this.t('fallbackNone') },
{ label: this.t('labelCertificateNo'), value: certificateNo },
// { label: '国籍', value: profile.country || profile.nationality || '-' },
// { label: '工作单位', value: profile.workUnit || '-' },
// { label: '兴趣爱好', value: hobbyText }
]
}
},
watch: {
language() {
this.refreshStatus()
}
},
onLoad(options = {}) {
this.sourceType = options.from || ''
this.targetCode = decodeURIComponent(options.code || '').trim()
const payloadText = options.payload ? decodeURIComponent(options.payload) : ''
if (!payloadText) {
return
}
try {
const payload = JSON.parse(payloadText)
if (payload && typeof payload === 'object') {
this.applicationInfo = payload
if (!this.targetCode) {
this.targetCode = resolveRegistrationApplicationCode(payload)
}
}
} catch (error) {
console.error('parse auth result payload failed', error)
}
},
async onShow() {
await fetchEnums(false, this.language)
this.refreshStatus()
},
methods: {
t(key) {
return this.$tm('pkgAccount.auth_result.auth_result', key)
},
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
})
},
async fetchApplicationByCode(providerValue, applicationCode) {
if (!providerValue || !applicationCode) {
return null
}
const response = await registrationApplicationMePage({
ProviderValue: providerValue,
Search: applicationCode,
PageIndex: 1,
PageSize: 20
}, this.language)
const pageData = formatPageResponse(response)
const list = Array.isArray(pageData.data) ? pageData.data : []
return list.find(item => resolveRegistrationApplicationCode(item) === applicationCode) || list[0] || null
},
async refreshStatus() {
this.loading = true
try {
const providerValue = await this.getWechatCode()
const applicationCode = this.applicationCode
let target = null
if (applicationCode) {
target = await this.fetchApplicationByCode(providerValue || '', applicationCode)
} else {
const response = await registrationMyorder({
providerValue: providerValue || ''
}, this.language)
const rawTarget = response && response.data && typeof response.data === 'object' ? response.data : response
target = rawTarget.data !== undefined ? rawTarget.data : rawTarget
}
if (!target || typeof target !== 'object' || !Object.keys(target).length) {
if (this.applicationInfo && this.applicationCode) {
uni.showToast({
title: this.t('latestDetailMissing'),
icon: 'none'
})
return
}
this.applicationInfo = {
approvalStatus: 1
}
uni.showToast({
title: this.t('approvedPleaseLogin'),
icon: 'success'
})
setTimeout(() => {
this.goToLogin()
}, 800)
return
}
this.applicationInfo = target
this.targetCode = resolveRegistrationApplicationCode(target) || this.targetCode
if (target.approvalStatus === 1) {
const approvedUser = buildUserFromRegistrationApplication(target, {
password: ''
})
saveRegisteredUser(approvedUser)
}
} catch (error) {
console.error('refresh registration status failed', error)
uni.showToast({
title: this.t('fetchStatusFailed'),
icon: 'none'
})
} finally {
this.loading = false
}
},
getApprovalStatusLabelLocal(status) {
return getApprovalStatusLabel(
status,
this.authStatus === 'pending'
? this.t('statusPending')
: this.authStatus === 'approved'
? this.t('statusApproved')
: this.authStatus === 'rejected'
? this.t('statusRejected')
: this.t('unsubmitted'),
this.language
)
},
goToLogin() {
uni.reLaunch({
url: '/pkgAccount/login/login?bypass=1'
})
},
resubmit() {
uni.redirectTo({
url: '/pkgAccount/register/register?bypass=1'
})
},
goToRegistrationRecords() {
uni.navigateTo({
url: '/pkgAccount/registration_records/registration_records'
})
},
}
}
</script>
<style lang="scss" scoped>
.auth-result-page {
/* 白蓝主题渐变背景 */
background: linear-gradient(180deg, #F0F6FF 0%, #F8FAFF 100%);
min-height: 100vh;
}
.status-card {
background: #ffffff;
border-radius: 24rpx;
box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.04);
border: 1rpx solid rgba($brand-primary, 0.05);
position: relative;
z-index: 1;
margin-top: 120rpx;
padding-bottom: 40rpx;
}
.status-icon-wrapper {
width: 140rpx;
height: 140rpx;
margin: 0 auto;
border-radius: 50%;
margin-top: -70rpx;
background: #ffffff;
box-shadow: 0 8rpx 24rpx rgba($brand-primary, 0.08);
}
.status-icon-wrapper.approved {
background: rgba($brand-primary, 0.1);
}
.status-icon-wrapper.rejected {
background: rgba(239, 68, 68, 0.1);
}
.status-icon-wrapper.pending,
.status-icon-wrapper.unsubmitted {
background: rgba(245, 158, 11, 0.12);
}
.status-icon {
font-size: 80rpx;
}
.icon-success {
color: $brand-primary;
}
.icon-rejected {
color: #cf3c3c;
}
.icon-pending,
.icon-unsubmitted {
color: #c98512;
}
.card-title {
font-size: 44rpx;
font-weight: bold;
color: #1f1f1f;
}
.card-desc {
font-size: 28rpx;
color: #888888;
line-height: 1.6;
}
.code-chip {
width: fit-content;
max-width: calc(100% - 64rpx);
margin-left: auto;
margin-right: auto;
padding: 12rpx 24rpx;
border-radius: 999rpx;
background: rgba($brand-primary, 0.08);
color: $brand-primary;
font-size: 24rpx;
font-weight: 600;
}
.reason-box {
padding: 30rpx;
border-radius: 20rpx;
}
.reason-box.approved {
background: rgba($brand-primary, 0.05);
border: 1rpx solid rgba($brand-primary, 0.1);
}
.reason-box.approved .reason-title,
.reason-box.approved .reason-content {
color: $brand-primary;
}
.reason-box.rejected {
background: rgba(239, 68, 68, 0.05);
border: 1rpx solid rgba(239, 68, 68, 0.1);
}
.reason-box.rejected .reason-title,
.reason-box.rejected .reason-content {
color: #ef4444;
}
.reason-box.pending,
.reason-box.unsubmitted {
background: rgba(245, 158, 11, 0.05);
border: 1rpx solid rgba(245, 158, 11, 0.1);
}
.reason-box.pending .reason-title,
.reason-box.pending .reason-content,
.reason-box.unsubmitted .reason-title,
.reason-box.unsubmitted .reason-content {
color: #f59e0b;
}
.reason-title {
font-size: 28rpx;
font-weight: bold;
}
.reason-content {
font-size: 26rpx;
line-height: 1.6;
}
.summary-box {
padding: 30rpx;
background: rgba($brand-primary, 0.02);
border-radius: 20rpx;
border: 1rpx solid rgba($brand-primary, 0.1);
}
.summary-title {
font-size: 30rpx;
font-weight: bold;
color: #1A2233;
}
.summary-row {
border-bottom: 1rpx dashed rgba($brand-primary, 0.15);
}
.summary-row:last-child {
border-bottom: none;
}
.summary-label {
font-size: 28rpx;
color: #6A7A99;
}
.summary-value {
font-size: 28rpx;
color: #1A2233;
font-weight: 500;
max-width: 65%;
text-align: right;
}
.custom-btn {
width: 100%;
height: 96rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 999rpx;
font-size: 32rpx;
transition: all 0.3s;
}
.custom-btn:active {
transform: scale(0.98);
}
.btn-primary {
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);
}
.btn-dark {
background: #ffffff;
color: $brand-primary;
border: 1rpx solid rgba($brand-primary, 0.3);
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.05);
}
.btn-light {
background: #ffffff;
color: #1A2233;
border: 1rpx solid rgba($brand-primary, 0.15);
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.04);
}
/* 动画效果 */
.fade-in {
animation: fadeIn 0.6s ease-out forwards;
}
.fade-in-up {
animation: fadeInUp 0.6s ease-out forwards;
animation-delay: 0.2s;
opacity: 0;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(40rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.spin {
display: inline-block;
animation: spin 1s linear infinite;
}
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
</style>