2026-04-10 01:49:07 +08:00
|
|
|
|
const auth = require('../../../utils/auth');
|
2026-04-02 18:02:17 +08:00
|
|
|
|
|
|
|
|
|
|
const PHONE_RE = /^1[3456789]\d{9}$/;
|
|
|
|
|
|
|
2026-04-10 16:10:33 +08:00
|
|
|
|
function normalizeAuthStatus(v) {
|
|
|
|
|
|
if (v == null || v === '') return 0;
|
|
|
|
|
|
const n = Number(v);
|
|
|
|
|
|
return Number.isNaN(n) ? 0 : n;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 0 未认证 | 1 审核中 | 2 审核通过 | 3 审核失败 | 4 已取消 */
|
|
|
|
|
|
function authStatusText(status) {
|
|
|
|
|
|
const map = {
|
|
|
|
|
|
0: '未认证',
|
|
|
|
|
|
1: '审核中',
|
|
|
|
|
|
2: '审核通过',
|
|
|
|
|
|
3: '审核失败',
|
|
|
|
|
|
4: '已取消'
|
|
|
|
|
|
};
|
|
|
|
|
|
return map[normalizeAuthStatus(status)] || '未认证';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function canSubmitAuth(status) {
|
|
|
|
|
|
const s = normalizeAuthStatus(status);
|
|
|
|
|
|
return s === 0 || s === 3;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 仅 0 未认证 显示「提交认证」,仅 3 审核失败 显示「重新提交认证」 */
|
|
|
|
|
|
function submitAuthButtonText(status) {
|
|
|
|
|
|
return normalizeAuthStatus(status) === 0 ? '提交认证' : '重新提交认证';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** authStatus 以 /api/my/profile 为准;profile 无字段时再尝试认证详情接口 */
|
|
|
|
|
|
function pickAuthStatus(profile, authDetail) {
|
|
|
|
|
|
const p = profile && typeof profile === 'object' ? profile : {};
|
|
|
|
|
|
const d = authDetail && typeof authDetail === 'object' ? authDetail : {};
|
|
|
|
|
|
const v =
|
|
|
|
|
|
p.authStatus != null
|
|
|
|
|
|
? p.authStatus
|
|
|
|
|
|
: p.AuthStatus != null
|
|
|
|
|
|
? p.AuthStatus
|
|
|
|
|
|
: d.authStatus != null
|
|
|
|
|
|
? d.authStatus
|
|
|
|
|
|
: d.AuthStatus;
|
|
|
|
|
|
return normalizeAuthStatus(v);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-02 18:02:17 +08:00
|
|
|
|
Page({
|
|
|
|
|
|
data: {
|
|
|
|
|
|
activeTab: 1, // 1=身份证 2=护照
|
|
|
|
|
|
|
2026-04-10 01:49:07 +08:00
|
|
|
|
// 上传图片 URL(远程)
|
2026-04-02 18:02:17 +08:00
|
|
|
|
idCardImageUrl: '',
|
|
|
|
|
|
idCardBackImageUrl: '',
|
|
|
|
|
|
passportImageUrl: '',
|
|
|
|
|
|
|
2026-04-10 01:49:07 +08:00
|
|
|
|
// 识别字段(只读,OCR 自动填充)
|
2026-04-02 18:02:17 +08:00
|
|
|
|
name: '',
|
|
|
|
|
|
idCardNumber: '',
|
|
|
|
|
|
birthDate: '',
|
|
|
|
|
|
genderOptions: [
|
2026-04-10 01:49:07 +08:00
|
|
|
|
{ value: 0, label: '男' },
|
|
|
|
|
|
{ value: 1, label: '女' }
|
2026-04-02 18:02:17 +08:00
|
|
|
|
],
|
|
|
|
|
|
genderIndex: 0,
|
2026-04-10 01:49:07 +08:00
|
|
|
|
|
|
|
|
|
|
// 护照识别额外字段(只读)
|
|
|
|
|
|
passportNumber: '',
|
|
|
|
|
|
nationality: '',
|
|
|
|
|
|
issueDate: '',
|
|
|
|
|
|
expiryDate: '',
|
|
|
|
|
|
|
|
|
|
|
|
// 联系方式
|
2026-04-02 18:02:17 +08:00
|
|
|
|
mobile: '',
|
|
|
|
|
|
verificationCode: '',
|
|
|
|
|
|
|
|
|
|
|
|
// UI 状态
|
2026-04-10 01:49:07 +08:00
|
|
|
|
recognizing: false,
|
2026-04-02 18:02:17 +08:00
|
|
|
|
submitting: false,
|
|
|
|
|
|
codeSending: false,
|
|
|
|
|
|
countdown: 0,
|
2026-04-10 16:10:33 +08:00
|
|
|
|
ocrDone: false,
|
|
|
|
|
|
authStatus: 0,
|
|
|
|
|
|
authStatusText: '未认证',
|
|
|
|
|
|
canSubmit: true,
|
|
|
|
|
|
submitBtnText: '提交认证'
|
2026-04-02 18:02:17 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
_timer: null,
|
|
|
|
|
|
|
|
|
|
|
|
onLoad() {
|
|
|
|
|
|
this.loadAuth();
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onShow() {
|
|
|
|
|
|
const accessToken = wx.getStorageSync('accessToken') || '';
|
|
|
|
|
|
if (!accessToken) {
|
|
|
|
|
|
wx.redirectTo({ url: '/subpackages/mine/login/login' });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onUnload() {
|
|
|
|
|
|
this._clearTimer();
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onPullDownRefresh() {
|
|
|
|
|
|
this.loadAuth().finally(() => wx.stopPullDownRefresh());
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onBack() {
|
|
|
|
|
|
if (getCurrentPages().length > 1) {
|
|
|
|
|
|
wx.navigateBack({ delta: 1 });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
wx.switchTab({ url: '/pages/mine/mine' });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onSwitchTab(e) {
|
|
|
|
|
|
const tab = Number(e.currentTarget.dataset.tab);
|
|
|
|
|
|
if (tab === this.data.activeTab) return;
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
activeTab: tab,
|
2026-04-10 01:49:07 +08:00
|
|
|
|
verificationCode: '',
|
|
|
|
|
|
ocrDone: false,
|
|
|
|
|
|
name: '', idCardNumber: '', birthDate: '', genderIndex: 0,
|
|
|
|
|
|
passportNumber: '', nationality: '', issueDate: '', expiryDate: '',
|
|
|
|
|
|
idCardImageUrl: '', idCardBackImageUrl: '', passportImageUrl: ''
|
2026-04-02 18:02:17 +08:00
|
|
|
|
});
|
2026-04-10 01:49:07 +08:00
|
|
|
|
this.loadAuth();
|
2026-04-02 18:02:17 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
loadAuth() {
|
2026-04-10 16:10:33 +08:00
|
|
|
|
return Promise.all([
|
|
|
|
|
|
auth.fetchMyAuth().catch(() => ({})),
|
|
|
|
|
|
auth.fetchMyProfile().catch(() => ({}))
|
|
|
|
|
|
]).then(([res, profile]) => {
|
|
|
|
|
|
const d = res && typeof res === 'object' ? res : {};
|
2026-04-13 18:10:43 +08:00
|
|
|
|
if(d.birthDate ){
|
|
|
|
|
|
d.birthDate = d.birthDate.split(' ')[0] || d.birthday || '';
|
|
|
|
|
|
}
|
2026-04-10 16:10:33 +08:00
|
|
|
|
const name = d.name || d.realName || '';
|
|
|
|
|
|
const mobile = d.mobile || d.phone || '';
|
|
|
|
|
|
const idCardNumber = d.idCardNumber || d.idNumber || '';
|
|
|
|
|
|
const idCardImageUrl = d.idCardImageUrl || d.idCardFrontImageUrl || '';
|
|
|
|
|
|
const idCardBackImageUrl = d.idCardBackImageUrl || '';
|
|
|
|
|
|
const passportImageUrl = d.passportPhotoUrl || d.passportImageUrl || '';
|
2026-04-13 18:10:43 +08:00
|
|
|
|
const birthDate = d.birthDate || d.birthday || '';
|
2026-04-10 16:10:33 +08:00
|
|
|
|
const rawGender = d.gender != null ? String(d.gender) : '';
|
|
|
|
|
|
const genderIndex = (rawGender === '1' || rawGender === 'F' || rawGender === '女') ? 1 : 0;
|
|
|
|
|
|
const ocrDone = Boolean(name || idCardNumber || passportImageUrl);
|
|
|
|
|
|
const authStatus = pickAuthStatus(profile, d);
|
|
|
|
|
|
const canSubmit = canSubmitAuth(authStatus);
|
|
|
|
|
|
const submitBtnText = submitAuthButtonText(authStatus);
|
|
|
|
|
|
const authStatusTextVal = authStatusText(authStatus);
|
|
|
|
|
|
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
name, mobile, idCardNumber,
|
|
|
|
|
|
idCardImageUrl, idCardBackImageUrl, passportImageUrl,
|
|
|
|
|
|
birthDate, genderIndex, ocrDone,
|
|
|
|
|
|
authStatus,
|
|
|
|
|
|
authStatusText: authStatusTextVal,
|
|
|
|
|
|
canSubmit,
|
|
|
|
|
|
submitBtnText
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2026-04-02 18:02:17 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onChooseImage(e) {
|
|
|
|
|
|
const kind = e.currentTarget.dataset.kind;
|
|
|
|
|
|
wx.chooseMedia({
|
|
|
|
|
|
count: 1,
|
|
|
|
|
|
mediaType: ['image'],
|
|
|
|
|
|
sourceType: ['album', 'camera'],
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
const tempFilePath = res.tempFiles && res.tempFiles[0] ? res.tempFiles[0].tempFilePath : '';
|
|
|
|
|
|
if (!tempFilePath) return;
|
2026-04-10 01:49:07 +08:00
|
|
|
|
this._uploadAndOcr(tempFilePath, kind);
|
2026-04-02 18:02:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-10 01:49:07 +08:00
|
|
|
|
async _uploadAndOcr(tempFilePath, kind) {
|
|
|
|
|
|
wx.showLoading({ title: '上传识别中...', mask: true });
|
|
|
|
|
|
this.setData({ recognizing: true });
|
|
|
|
|
|
try {
|
|
|
|
|
|
const imageUrl = await auth.uploadFile(tempFilePath);
|
|
|
|
|
|
|
|
|
|
|
|
if (kind === 'idFront') this.setData({ idCardImageUrl: imageUrl });
|
|
|
|
|
|
else if (kind === 'idBack') this.setData({ idCardBackImageUrl: imageUrl });
|
|
|
|
|
|
else if (kind === 'passport') this.setData({ passportImageUrl: imageUrl });
|
|
|
|
|
|
|
|
|
|
|
|
if (kind === 'idFront') {
|
|
|
|
|
|
const ocr = await auth.ocrIdCard(imageUrl);
|
|
|
|
|
|
if (ocr && ocr.success === false) {
|
|
|
|
|
|
wx.showToast({ title: ocr.message || '身份证识别失败', icon: 'none' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const genderIndex = (ocr && (ocr.gender === '女' || ocr.gender === 'F' || ocr.gender === '1')) ? 1 : 0;
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
name: (ocr && ocr.name) || '',
|
|
|
|
|
|
idCardNumber: (ocr && ocr.idCardNumber) || '',
|
|
|
|
|
|
birthDate: (ocr && ocr.birthDate) || '',
|
|
|
|
|
|
genderIndex,
|
|
|
|
|
|
ocrDone: true
|
|
|
|
|
|
});
|
|
|
|
|
|
wx.showToast({ title: '识别成功', icon: 'success' });
|
|
|
|
|
|
} else if (kind === 'passport') {
|
|
|
|
|
|
const ocr = await auth.ocrPassport(imageUrl);
|
|
|
|
|
|
if (ocr && ocr.success === false) {
|
|
|
|
|
|
wx.showToast({ title: ocr.message || '护照识别失败', icon: 'none' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const genderIndex = (ocr && (ocr.gender === '女' || ocr.gender === 'F' || ocr.gender === '1')) ? 1 : 0;
|
|
|
|
|
|
this.setData({
|
|
|
|
|
|
name: (ocr && ocr.name) || '',
|
|
|
|
|
|
passportNumber: (ocr && ocr.passportNumber) || '',
|
|
|
|
|
|
nationality: (ocr && ocr.nationality) || '',
|
|
|
|
|
|
birthDate: (ocr && (ocr.birthDate || ocr.birthDateYmd)) || '',
|
|
|
|
|
|
issueDate: (ocr && (ocr.issueDate || ocr.issueDateYmd)) || '',
|
|
|
|
|
|
expiryDate: (ocr && (ocr.expiryDate || ocr.validToDate)) || '',
|
|
|
|
|
|
genderIndex,
|
|
|
|
|
|
ocrDone: true
|
|
|
|
|
|
});
|
|
|
|
|
|
wx.showToast({ title: '识别成功', icon: 'success' });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
wx.showToast({ title: '上传成功', icon: 'success' });
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
wx.showToast({ title: (err && err.message) || '识别失败', icon: 'none' });
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.setData({ recognizing: false });
|
2026-04-13 23:56:07 +08:00
|
|
|
|
// wx.hideLoading();
|
2026-04-10 01:49:07 +08:00
|
|
|
|
}
|
2026-04-02 18:02:17 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onMobileInput(e) {
|
|
|
|
|
|
this.setData({ mobile: (e.detail.value || '').trim() });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onCodeInput(e) {
|
|
|
|
|
|
this.setData({ verificationCode: (e.detail.value || '').trim() });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onSendCodeTap() {
|
|
|
|
|
|
const { mobile, countdown, codeSending } = this.data;
|
|
|
|
|
|
if (codeSending || countdown > 0) return;
|
|
|
|
|
|
if (!PHONE_RE.test(mobile)) {
|
|
|
|
|
|
wx.showToast({ title: '请输入正确手机号', icon: 'none' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.setData({ codeSending: true });
|
|
|
|
|
|
wx.showLoading({ title: '发送中...' });
|
|
|
|
|
|
auth
|
|
|
|
|
|
.sendVerificationCode(mobile)
|
|
|
|
|
|
.then(() => {
|
|
|
|
|
|
wx.showToast({ title: '验证码已发送', icon: 'success' });
|
|
|
|
|
|
this._startCountdown(60);
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
wx.showToast({ title: '发送失败', icon: 'none' });
|
|
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
|
|
|
|
|
this.setData({ codeSending: false });
|
2026-04-13 23:56:07 +08:00
|
|
|
|
// wx.hideLoading();
|
2026-04-02 18:02:17 +08:00
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
_startCountdown(sec) {
|
|
|
|
|
|
this._clearTimer();
|
|
|
|
|
|
this.setData({ countdown: sec });
|
|
|
|
|
|
this._timer = setInterval(() => {
|
|
|
|
|
|
const next = this.data.countdown - 1;
|
|
|
|
|
|
if (next <= 0) {
|
|
|
|
|
|
this._clearTimer();
|
|
|
|
|
|
this.setData({ countdown: 0 });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.setData({ countdown: next });
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
_clearTimer() {
|
|
|
|
|
|
if (this._timer) {
|
|
|
|
|
|
clearInterval(this._timer);
|
|
|
|
|
|
this._timer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onSubmitTap() {
|
|
|
|
|
|
if (this.data.submitting) return;
|
|
|
|
|
|
|
|
|
|
|
|
const {
|
2026-04-10 01:49:07 +08:00
|
|
|
|
activeTab, idCardImageUrl, passportImageUrl,
|
|
|
|
|
|
name, idCardNumber, birthDate,
|
2026-04-13 18:10:43 +08:00
|
|
|
|
genderOptions, genderIndex, mobile, verificationCode,idCardBackImageUrl
|
2026-04-02 18:02:17 +08:00
|
|
|
|
} = this.data;
|
|
|
|
|
|
|
|
|
|
|
|
if (!name) {
|
2026-04-10 01:49:07 +08:00
|
|
|
|
wx.showToast({ title: '请先上传证件图片完成识别', icon: 'none' });
|
2026-04-02 18:02:17 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!PHONE_RE.test(mobile)) {
|
|
|
|
|
|
wx.showToast({ title: '请输入正确手机号', icon: 'none' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!verificationCode) {
|
|
|
|
|
|
wx.showToast({ title: '请输入验证码', icon: 'none' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-10 01:49:07 +08:00
|
|
|
|
const genderValue = genderOptions[genderIndex] ? genderOptions[genderIndex].value : 0;
|
|
|
|
|
|
|
|
|
|
|
|
let payload;
|
2026-04-02 18:02:17 +08:00
|
|
|
|
if (activeTab === 1) {
|
2026-04-10 01:49:07 +08:00
|
|
|
|
if (!idCardImageUrl) {
|
|
|
|
|
|
wx.showToast({ title: '请上传身份证人像面', icon: 'none' });
|
2026-04-02 18:02:17 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!idCardNumber) {
|
2026-04-10 01:49:07 +08:00
|
|
|
|
wx.showToast({ title: '请先完成身份证识别', icon: 'none' });
|
2026-04-02 18:02:17 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-10 01:49:07 +08:00
|
|
|
|
payload = {
|
2026-04-13 15:28:42 +08:00
|
|
|
|
authType: 1,
|
2026-04-10 01:49:07 +08:00
|
|
|
|
name,
|
|
|
|
|
|
mobile,
|
|
|
|
|
|
smsCode: verificationCode,
|
|
|
|
|
|
gender: genderValue,
|
|
|
|
|
|
birthDate: birthDate || undefined,
|
|
|
|
|
|
idCardNumber,
|
2026-04-13 18:10:43 +08:00
|
|
|
|
idCardImageUrl,
|
|
|
|
|
|
idCardBackImageUrl
|
2026-04-10 01:49:07 +08:00
|
|
|
|
};
|
2026-04-02 18:02:17 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
if (!passportImageUrl) {
|
|
|
|
|
|
wx.showToast({ title: '请上传护照', icon: 'none' });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!birthDate) {
|
2026-04-10 01:49:07 +08:00
|
|
|
|
wx.showToast({ title: '请先完成护照识别', icon: 'none' });
|
2026-04-02 18:02:17 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-10 01:49:07 +08:00
|
|
|
|
payload = {
|
2026-04-13 15:28:42 +08:00
|
|
|
|
authType: 2,
|
2026-04-10 01:49:07 +08:00
|
|
|
|
name,
|
|
|
|
|
|
mobile,
|
|
|
|
|
|
smsCode: verificationCode,
|
|
|
|
|
|
gender: genderValue,
|
|
|
|
|
|
birthDate,
|
|
|
|
|
|
passportPhotoUrl: passportImageUrl
|
|
|
|
|
|
};
|
2026-04-02 18:02:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.setData({ submitting: true });
|
|
|
|
|
|
wx.showLoading({ title: '提交中...' });
|
|
|
|
|
|
|
|
|
|
|
|
auth
|
|
|
|
|
|
.submitMyAuth(payload)
|
|
|
|
|
|
.then(() => {
|
|
|
|
|
|
wx.showToast({ title: '提交成功', icon: 'success' });
|
2026-04-13 18:10:43 +08:00
|
|
|
|
this.loadAuth();
|
|
|
|
|
|
setTimeout(() => wx.navigateBack({ delta: 1 }), 1500);
|
2026-04-02 18:02:17 +08:00
|
|
|
|
})
|
|
|
|
|
|
.catch((err) => {
|
|
|
|
|
|
wx.showToast({ title: (err && err.message) || '提交失败', icon: 'none' });
|
|
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
|
|
|
|
|
this.setData({ submitting: false });
|
2026-04-13 23:56:07 +08:00
|
|
|
|
// wx.hideLoading();
|
2026-04-02 18:02:17 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|