882 lines
27 KiB
JavaScript
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.

const { API } = require('../../utils/constants');
const { request } = require('../../utils/http');
const { formatDateTime } = require('../../utils/format');
const auth = require('../../utils/auth');
// 中文数字映射(项目一、二、三…)
const CN_NUM = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
/**
* 将 projects 数组转换为按项目分组的结构
* 每个项目下展开所有 groups,附带 selected 状态
*/
function getEventStatus(detail) {
if (!detail || typeof detail !== 'object') return null;
const v = detail.eventStatus != null ? detail.eventStatus : detail.EventStatus;
if (v == null || v === '') return null;
const n = Number(v);
return Number.isNaN(n) ? null : n;
}
/** 仅 1=报名中 可进入报名页;与后端 EventStatus 枚举一致 */
function toastForBlockedEventStatus(es) {
if (es === 0) return '赛事未开始';
if (es === 2) return '报名已结束';
if (es === 3) return '比赛进行中,暂不可报名';
if (es === 4) return '赛事已结束';
return '暂不可报名';
}
function buildProjectGroups(projects) {
return projects.map((p, idx) => {
const groups = Array.isArray(p.groups) ? p.groups : [];
return {
projectId: p.id || p.Id || idx,
projectName: p.name || p.Name || '-',
index: CN_NUM[idx] || String(idx + 1),
fee: p.fee != null ? p.fee : 0,
groups: groups.map((g) => ({
groupId: g.groupId || g.GroupId || g.id,
groupName: g.groupName || g.GroupName || '-',
ageRange: g.ageRange || g.AgeRange || '',
gender: g.gender || g.Gender || '',
fee: g.fee != null ? g.fee : (p.fee != null ? p.fee : 0),
selected: false,
ageRequirement: g.ageRequirement || g.AgeRequirement || '',
genderRequirement: g.genderRequirement != null ? g.genderRequirement : g.GenderRequirement != null ? g.GenderRequirement : '',
genderRequirementText: genderRequirementText(g.genderRequirement != null ? g.genderRequirement : g.GenderRequirement)
}))
};
});
}
/** 计算已选组别的总价 */
function calcTotalPrice(projectGroups) {
let total = 0;
projectGroups.forEach((p) => {
p.groups.forEach((g) => {
if (g.selected) total += Number(g.fee) || 0;
});
});
return total;
}
function toNonNegInt(v) {
const n = Number(v);
if (Number.isNaN(n)) return null;
const i = Math.floor(n);
return i >= 0 ? i : null;
}
function genderRequirementText(v) {
if (v === undefined || v === null || v === '') return '不限';
const s = String(v).trim();
if (s === '0' || s === '男' || s.toUpperCase() === 'M') return '男';
if (s === '1' || s === '女' || s.toUpperCase() === 'F') return '女';
if (s === '2' || s === '不限' || s === '全部') return '不限';
return s;
}
function normalizeGenderToNum(v) {
if (v === undefined || v === null || v === '') return null;
const s = String(v).trim();
if (s === '0' || s === '男' || s.toUpperCase() === 'M') return 0;
if (s === '1' || s === '女' || s.toUpperCase() === 'F') return 1;
if (s === '2' || s === '不限' || s === '全部') return 2;
const n = Number(s);
if (!Number.isNaN(n) && (n === 0 || n === 1 || n === 2)) return n;
return null;
}
function parseYmdDate(s) {
if (!s) return null;
const str = String(s).trim();
const m = str.match(/^(\d{4})-(\d{2})-(\d{2})/);
if (!m) return null;
const y = Number(m[1]);
const mo = Number(m[2]);
const d = Number(m[3]);
if ([y, mo, d].some((x) => Number.isNaN(x))) return null;
const dt = new Date(y, mo - 1, d);
return Number.isNaN(dt.getTime()) ? null : dt;
}
function parseYmdDateAny(text) {
if (!text) return null;
const s = String(text);
const m = s.match(/(\d{4})-(\d{2})-(\d{2})/);
if (!m) return null;
const y = Number(m[1]);
const mo = Number(m[2]);
const d = Number(m[3]);
if ([y, mo, d].some((x) => Number.isNaN(x))) return null;
const dt = new Date(y, mo - 1, d);
return Number.isNaN(dt.getTime()) ? null : dt;
}
function calcAgeYears(birthDateStr, atDateStr) {
const b = parseYmdDate(birthDateStr);
if (!b) return null;
const at = parseYmdDate(atDateStr) || new Date();
let age = at.getFullYear() - b.getFullYear();
const m = at.getMonth() - b.getMonth();
if (m < 0 || (m === 0 && at.getDate() < b.getDate())) age -= 1;
return age >= 0 ? age : null;
}
function parseBirthDateRangeText(text) {
// e.g. "2021-03-31 至 2026-04-29" / "2021-03-31~2026-04-29"
if (!text) return null;
const s = String(text);
const ms = s.match(/(\d{4}-\d{2}-\d{2})/g);
if (!ms || ms.length < 2) return null;
const a = parseYmdDate(ms[0]);
const b = parseYmdDate(ms[1]);
if (!a || !b) return null;
const start = a <= b ? a : b;
const end = a <= b ? b : a;
return { start, end, startText: ms[0], endText: ms[1] };
}
function parseAgeYearsRangeText(text) {
// 兼容旧格式:如 "6-12岁" / "6~12"
if (!text) return null;
const s = String(text);
const nums = s.match(/\d+/g);
if (!nums || nums.length === 0) return null;
const a = Number(nums[0]);
if (Number.isNaN(a)) return null;
if (nums.length === 1) return { min: a, max: a };
const b = Number(nums[1]);
if (Number.isNaN(b)) return { min: a, max: a };
return { min: Math.min(a, b), max: Math.max(a, b) };
}
function isIndividualProject(p) {
const t = (p && (p.typeName || p.TypeName)) || '';
const s = String(t);
// 只显示个人项目:个人速度、个人花样等
return s.includes('个人');
}
function projectTypeOf(rawProject) {
const t = (rawProject && (rawProject.typeName || rawProject.TypeName)) || '';
const s = String(t);
if (s.includes('团队')) return 'team';
if (s.includes('个人')) return 'individual';
return 'unknown';
}
function countSelectedByType(projectGroups, rawProjects) {
const map = new Map();
(rawProjects || []).forEach((p) => {
const pid = Number(p.id != null ? p.id : p.Id);
if (!Number.isNaN(pid)) map.set(pid, projectTypeOf(p));
});
let individual = 0;
let team = 0;
let unknown = 0;
projectGroups.forEach((pg) => {
const pid = Number(pg.projectId);
const type = map.get(pid) || 'unknown';
pg.groups.forEach((g) => {
if (!g.selected) return;
if (type === 'individual') individual += 1;
else if (type === 'team') team += 1;
else unknown += 1;
});
});
return { individual, team, unknown };
}
function buildCalculateAmountItems(projectGroups) {
const map = new Map(); // projectId -> personCount
projectGroups.forEach((p) => {
const pid = Number(p.projectId);
if (Number.isNaN(pid)) return;
let cnt = 0;
p.groups.forEach((g) => {
if (g.selected) cnt += 1;
});
if (cnt > 0) map.set(pid, (map.get(pid) || 0) + cnt);
});
return Array.from(map.entries()).map(([projectId, personCount]) => ({
projectId,
personCount
}));
}
Page({
data: {
eventId: null,
loggedIn: false,
eventDetail: null,
projects: [],
projectGroups: [], // 分组后的展示数据
couponCode: '',
totalPrice: 0,
originalPrice: 0,
discountPrice: 0,
usedCouponId: null,
submitting: false,
openidLoading: false,
myAuth: null
},
_calcTimer: null,
_calcSeq: 0,
onLoad(options) {
const eventId = Number(options.eventId);
this.setData({ eventId });
this.fetchDetail(eventId);
},
onShow() {
const accessToken = wx.getStorageSync('accessToken') || '';
const loggedIn = Boolean(accessToken);
this.setData({ loggedIn });
if (loggedIn) this._ensureMyAuth();
},
onBack() {
if (getCurrentPages().length > 1) {
wx.navigateBack();
} else {
wx.switchTab({ url: '/pages/home/home' });
}
},
fetchDetail(eventId) {
if (!eventId) return;
request({
path: API.eventDetail(eventId),
method: 'GET',
needAuth: false
})
.then((detail) => {
const es = getEventStatus(detail);
console.log( 'es', es);
if (es !== null && es !== 1) {
wx.showToast({ title: toastForBlockedEventStatus(es), icon: 'none' });
setTimeout(() => {
if (getCurrentPages().length > 1) {
wx.navigateBack();
} else {
wx.switchTab({ url: '/pages/home/home' });
}
}, 1600);
return;
}
const rawProjects = Array.isArray(detail.projects) ? detail.projects : [];
const projects = rawProjects.filter(isIndividualProject);
const projectGroups = buildProjectGroups(projects);
this.setData({
eventDetail: detail,
projects,
projectGroups,
totalPrice: 0
});
})
.catch(() => {
wx.showToast({ title: '加载失败', icon: 'none' });
});
},
_ensureMyAuth() {
// 认证信息用于年龄/性别限制校验;没有认证则引导去个人中心
return auth
.fetchMyAuth()
.then((res) => {
const d = res && typeof res === 'object' ? res : null;
const birthDate = d ? (d.birthDate || d.birthday || '') : '';
if (!birthDate) {
wx.showModal({
title: '需先完成认证',
content: '报名个人项目需要先完成身份认证(用于校验年龄与性别限制)。',
confirmText: '去个人中心',
cancelText: '取消',
success: (r) => {
if (r.confirm) wx.switchTab({ url: '/pages/mine/mine' });
}
});
this.setData({ myAuth: null });
return null;
}
this.setData({ myAuth: d });
return d;
})
.catch(() => {
wx.showModal({
title: '获取认证信息失败',
content: '请稍后重试,或前往个人中心完成认证。',
confirmText: '去个人中心',
cancelText: '取消',
success: (r) => {
if (r.confirm) wx.switchTab({ url: '/pages/mine/mine' });
}
});
this.setData({ myAuth: null });
return null;
});
},
onGoMineLogin() {
wx.switchTab({ url: '/pages/mine/mine' });
},
_showOpenidResult(openid) {
wx.showModal({
title: 'openid',
content: String(openid),
confirmText: '复制',
success: (r) => {
if (r.confirm) {
wx.setClipboardData({ data: String(openid) });
}
}
});
},
/** 优先读本地登录缓存;没有则 wx.login + 后端换 openid */
onFetchOpenId() {
if (this.data.openidLoading) return;
const app = getApp();
const cached =
(app && app.globalData && app.globalData.openid) || wx.getStorageSync('openid') || '';
if (cached) {
this._showOpenidResult(cached);
return;
}
this.setData({ openidLoading: true });
wx.showLoading({ title: '获取中', mask: true });
wx.login({
success: (loginRes) => {
const code = loginRes.code;
if (!code) {
wx.hideLoading();
this.setData({ openidLoading: false });
wx.showToast({ title: '未获取到 code', icon: 'none' });
return;
}
request({
path: API.wechatOpenidByJsCode,
method: 'POST',
needAuth: false,
data: { code },
contentType: 'application/json'
})
.then((data) => {
const oid =
data && typeof data === 'object'
? data.openid || data.OpenId || data.openId || ''
: '';
const openid = typeof oid === 'string' ? oid : '';
if (openid && app && typeof app.setAuth === 'function') {
app.setAuth({ openid });
}
wx.hideLoading();
this.setData({ openidLoading: false });
if (openid) {
this._showOpenidResult(openid);
} else {
wx.showToast({ title: '接口未返回 openid', icon: 'none' });
}
})
.catch(() => {
wx.hideLoading();
this.setData({ openidLoading: false });
wx.showModal({
title: '换取 openid 失败',
content: `请确认后端接口路径:${API.wechatOpenidByJsCode}\n\n可临时复制 code 给服务端调试:\n${code}`,
confirmText: '复制 code',
cancelText: '关闭',
success: (r) => {
if (r.confirm) wx.setClipboardData({ data: code });
}
});
});
},
fail: () => {
wx.hideLoading();
this.setData({ openidLoading: false });
wx.showToast({ title: 'wx.login 失败', icon: 'none' });
}
});
},
/** 点击组别标签切换选中状态 */
onGroupTap(e) {
const { projectId, groupId } = e.currentTarget.dataset;
if (!this.data.myAuth) {
wx.showToast({ title: '请先完成身份认证', icon: 'none' });
wx.switchTab({ url: '/pages/mine/mine' });
return;
}
// 年龄/性别限制校验
const my = this.data.myAuth || {};
const birthDate = my.birthDate || my.birthday || '';
const myGender = normalizeGenderToNum(my.gender != null ? my.gender : my.sex);
const eventStart = (this.data.eventDetail && this.data.eventDetail.matchStartTime) || '';
const clickedProject = this.data.projectGroups.find((p) => p.projectId === projectId);
const clickedGroup = clickedProject ? clickedProject.groups.find((g) => g.groupId === groupId) : null;
if (clickedGroup) {
const reqGender = normalizeGenderToNum(clickedGroup.genderRequirement);
if (reqGender != null && reqGender !== 2 && myGender != null && reqGender !== myGender) {
wx.showToast({ title: `该组别仅限${genderRequirementText(reqGender)}报名`, icon: 'none' });
return;
}
// 年龄限制:优先支持“出生日期范围”,兼容旧“岁数范围”
const birthRange = parseBirthDateRangeText(clickedGroup.ageRequirement);
if (birthRange) {
const bd = parseYmdDateAny(birthDate);
if (!bd) {
wx.showToast({ title: '无法解析出生日期,请先完善认证信息', icon: 'none' });
return;
}
if (bd < birthRange.start || bd > birthRange.end) {
wx.showToast({
title: `出生日期需在${birthRange.startText}至${birthRange.endText}`,
icon: 'none'
});
return;
}
} else {
const range = parseAgeYearsRangeText(clickedGroup.ageRequirement);
if (range) {
const age = calcAgeYears(birthDate, eventStart);
if (age == null) {
wx.showToast({ title: '无法解析出生日期,请先完善认证信息', icon: 'none' });
return;
}
if (age < range.min || age > range.max) {
wx.showToast({ title: `年龄需在${range.min}-${range.max}岁`, icon: 'none' });
return;
}
}
}
}
const nextProjectGroups = this.data.projectGroups.map((p) => {
if (p.projectId !== projectId) return p;
return {
...p,
groups: p.groups.map((g) => {
if (g.groupId !== groupId) return g;
return { ...g, selected: !g.selected };
})
};
});
const detail = this.data.eventDetail || {};
const individualLimit = toNonNegInt(detail.individualProjectLimit);
const teamLimit = toNonNegInt(detail.teamProjectLimit);
const { individual, team } = countSelectedByType(nextProjectGroups, this.data.projects);
if (individualLimit != null && individual > individualLimit) {
wx.showToast({ title: `个人项目最多选择${individualLimit}个`, icon: 'none' });
return;
}
if (teamLimit != null && team > teamLimit) {
wx.showToast({ title: `团队项目最多选择${teamLimit}个`, icon: 'none' });
return;
}
this.setData({ projectGroups: nextProjectGroups }, () => {
this._scheduleRecalcAmount();
});
},
_scheduleRecalcAmount() {
if (this._calcTimer) {
clearTimeout(this._calcTimer);
this._calcTimer = null;
}
this._calcTimer = setTimeout(() => {
this._calcTimer = null;
this._recalcAmountNow();
}, 250);
},
_recalcAmountNow() {
const items = buildCalculateAmountItems(this.data.projectGroups);
if (!items.length) {
this.setData({
totalPrice: 0,
originalPrice: 0,
discountPrice: 0,
usedCouponId: null
});
return;
}
const seq = (this._calcSeq += 1);
request({
path: API.calculateAmount,
method: 'POST',
needAuth: true,
data: {
eventId: Number(this.data.eventId),
registrationMode: 1,
items
},
contentType: 'application/json'
})
.then((res) => {
if (seq !== this._calcSeq) return;
let totalAmount = null;
let originalPrice = 0;
let discountPrice = 0;
if (typeof res === 'number') {
totalAmount = res;
originalPrice = res;
} else if (typeof res === 'string') {
const n = Number(res);
totalAmount = Number.isNaN(n) ? null : n;
originalPrice = totalAmount || 0;
} else if (res && typeof res === 'object') {
// 从统一的 CalculateAmountOutput 中获取数据
totalAmount = res.totalAmount != null ? Number(res.totalAmount) :
(res.amount != null ? Number(res.amount) :
(res.payAmount != null ? Number(res.payAmount) : null));
originalPrice = res.originalAmount != null ? Number(res.originalAmount) : totalAmount || 0;
discountPrice = res.discountAmount != null ? Number(res.discountAmount) : 0;
}
if (totalAmount == null) {
// fallback:仍然展示前端计算的费用,避免空白
const fallbackPrice = calcTotalPrice(this.data.projectGroups);
this.setData({
totalPrice: fallbackPrice,
originalPrice: fallbackPrice,
discountPrice: 0
});
} else {
this.setData({
totalPrice: totalAmount,
originalPrice: originalPrice || totalAmount,
discountPrice: discountPrice || 0
});
}
})
.catch(() => {
if (seq !== this._calcSeq) return;
const fallbackPrice = calcTotalPrice(this.data.projectGroups);
this.setData({
totalPrice: fallbackPrice,
originalPrice: fallbackPrice,
discountPrice: 0
});
});
},
onCouponInput(e) {
this.setData({ couponCode: (e.detail.value || '').trim() });
},
/**
* 验证并应用优惠券
*/
async onValidateCoupon() {
const couponCode = this.data.couponCode;
if (!couponCode) {
wx.showToast({ title: '请输入优惠券兑换码', icon: 'none' });
return;
}
wx.showLoading({ title: '验证中', mask: true });
try {
const res = await request({
path: API.couponValidate,
method: 'POST',
needAuth: true,
data: {
eventId: Number(this.data.eventId),
couponCode: couponCode,
originalAmount: this.data.originalPrice || this.data.totalPrice
},
contentType: 'application/json'
});
if (res && res.isValid) {
this.setData({
usedCouponId: res.couponId,
totalPrice: res.finalAmount,
discountPrice: res.discountAmount || 0,
originalPrice: res.originalAmount || this.data.originalPrice
});
wx.showToast({ title: '优惠券应用成功', icon: 'success' });
} else {
wx.showToast({ title: res.invalidReason || '优惠券无效', icon: 'none' });
}
} catch (error) {
wx.showToast({
title: error && error.message ? error.message : '验证失败,请稍后重试',
icon: 'none'
});
} finally {
wx.hideLoading();
}
},
/** 收集已选的 projectId + groupId */
_getSelectedItems() {
const items = [];
this.data.projectGroups.forEach((p) => {
p.groups.forEach((g) => {
if (g.selected) {
items.push({ projectId: Number(p.projectId), groupId: Number(g.groupId) });
}
});
});
return items;
},
_validateAndGetPayload(paymentMethod) {
if (!this.data.loggedIn) {
wx.showToast({ title: '请先登录', icon: 'none' });
return null;
}
const items = this._getSelectedItems();
if (items.length === 0) {
wx.showToast({ title: '请至少选择一个组别', icon: 'none' });
return null;
}
const detail = this.data.eventDetail || {};
const individualLimit = toNonNegInt(detail.individualProjectLimit);
const teamLimit = toNonNegInt(detail.teamProjectLimit);
const { individual, team, unknown } = countSelectedByType(this.data.projectGroups, this.data.projects);
if (unknown > 0 && (individualLimit != null || teamLimit != null)) {
// typeName 识别不出来时,不阻断提交,但提醒后端应补齐 typeName
console.warn('存在未识别项目类型的选项,无法严格执行个人/团队限报', { unknown });
}
if (individualLimit != null && individual > individualLimit) {
wx.showToast({ title: `个人项目最多选择${individualLimit}个`, icon: 'none' });
return null;
}
if (teamLimit != null && team > teamLimit) {
wx.showToast({ title: `团队项目最多选择${teamLimit}个`, icon: 'none' });
return null;
}
return {
eventId: Number(this.data.eventId),
registrationMode: 1,
paymentMethod, // 1=线下 2=线上
couponCode: this.data.couponCode || null,
items
};
},
/** 线下支付 */
onPayOffline() {
const payload = this._validateAndGetPayload(1);
if (!payload) return;
this._submitApply(payload);
},
/** 线上支付 */
async onPayOnline() {
const payload = this._validateAndGetPayload(2);
if (!payload) return;
try {
// 1. 获取 openid
const openid = await this._getOpenId();
if (!openid) {
wx.showToast({ title: '获取 openid 失败', icon: 'none' });
return;
}
// 2. 提交报名
this.setData({ submitting: true });
const registrationId = await this._submitApplyForPayment(payload);
// 3. 选择支付方式并获取支付参数
await this._selectPaymentMethod(registrationId, openid, 2);
// 4. 获取支付信息
const paymentInfo = await this._getPaymentInfo(registrationId, openid);
// 5. 调起微信支付
await this._invokeWeChatPay(paymentInfo.wechatPayParams, registrationId);
} catch (error) {
console.error('支付流程异常:', error);
wx.showToast({
title: error && error.message ? error.message : '支付失败,请稍后重试',
icon: 'none'
});
} finally {
this.setData({ submitting: false });
}
},
/** 获取 openid */
async _getOpenId() {
const app = getApp();
const cached =
(app && app.globalData && app.globalData.openid) || wx.getStorageSync('openid') || '';
if (cached) {
return cached;
}
return new Promise((resolve, reject) => {
wx.login({
success: (loginRes) => {
const code = loginRes.code;
if (!code) {
reject(new Error('未获取到 code'));
return;
}
request({
path: API.wechatOpenidByJsCode,
method: 'POST',
needAuth: false,
data: { code },
contentType: 'application/json'
})
.then((data) => {
const oid =
data && typeof data === 'object'
? data.openId || data.OpenId || data.openid || ''
: '';
const openid = typeof oid === 'string' ? oid : '';
if (openid && app && typeof app.setAuth === 'function') {
app.setAuth({ openid });
}
if (openid) {
resolve(openid);
} else {
reject(new Error('接口未返回 openid'));
}
})
.catch((err) => {
reject(err);
});
},
fail: () => {
reject(new Error('wx.login 失败'));
}
});
});
},
/** 提交报名(用于支付流程) */
_submitApplyForPayment(payload) {
return request({
path: API.apply,
method: 'POST',
needAuth: true,
data: payload,
contentType: 'application/json'
});
},
/** 选择支付方式 */
_selectPaymentMethod(registrationId, openId, paymentMethod) {
return request({
path: '/api/eventclient/select-payment-method',
method: 'POST',
needAuth: true,
data: { registrationId, openId, paymentMethod },
contentType: 'application/json'
});
},
/** 获取支付信息 */
_getPaymentInfo(registrationId, openid) {
return request({
path: '/api/eventclient/payment-info',
method: 'POST',
needAuth: true,
data: {
registrationId,
openId: openid,
couponId: this.data.usedCouponId
},
contentType: 'application/json'
});
},
/** 调起微信支付 */
_invokeWeChatPay(wechatPayParams, registrationId) {
return new Promise((resolve, reject) => {
wx.requestPayment({
...wechatPayParams,
success: async () => {
try {
// 支付成功,同步支付状态
await request({
path: `/api/eventclient/sync-payment/${registrationId}`,
method: 'POST',
needAuth: true,
data: {},
contentType: 'application/json'
});
wx.showToast({ title: '支付成功', icon: 'success' });
setTimeout(() => {
wx.switchTab({ url: '/pages/mine/mine' });
}, 1500);
resolve();
} catch (err) {
console.error('同步支付状态失败:', err);
wx.showModal({
title: '提示',
content: '支付成功,但同步状态失败,请稍后在订单列表中查看',
showCancel: false,
success: () => {
wx.switchTab({ url: '/pages/mine/mine' });
}
});
resolve();
}
},
fail: (err) => {
console.error('微信支付失败:', err);
if (err.errMsg && err.errMsg.includes('cancel')) {
wx.showToast({ title: '支付已取消', icon: 'none' });
} else {
wx.showToast({
title: err.errMsg || '支付失败,请稍后重试',
icon: 'none'
});
}
reject(err);
}
});
});
},
_submitApply(payload) {
this.setData({ submitting: true });
request({
path: API.apply,
method: 'POST',
needAuth: true,
data: payload,
contentType: 'application/json'
})
.then(() => {
wx.showToast({ title: '报名成功', icon: 'success' });
setTimeout(() => wx.switchTab({ url: '/pages/mine/mine' }), 800);
})
.catch((err) => {
wx.showToast({ title: err && err.message ? err.message : '提交失败', icon: 'none' });
})
.finally(() => this.setData({ submitting: false }));
}
});