From c21de31c69cd5853fbd1b85833b65d7209bb5328 Mon Sep 17 00:00:00 2001 From: ltx <1006158920@qq.com> Date: Tue, 7 Apr 2026 17:54:54 +0800 Subject: [PATCH] =?UTF-8?q?update:=E6=8A=A5=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/apply/apply.js | 140 ++++++++++++++++++++++++++++++++++++++++--- utils/constants.js | 1 + utils/http.js | 1 + 3 files changed, 133 insertions(+), 9 deletions(-) diff --git a/pages/apply/apply.js b/pages/apply/apply.js index 624611c..b86db8a 100644 --- a/pages/apply/apply.js +++ b/pages/apply/apply.js @@ -101,6 +101,19 @@ function parseYmdDate(s) { 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; @@ -111,7 +124,22 @@ function calcAgeYears(birthDateStr, atDateStr) { return age >= 0 ? age : null; } -function parseAgeRangeText(text) { +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); @@ -162,6 +190,23 @@ function countSelectedByType(projectGroups, rawProjects) { 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, @@ -176,6 +221,9 @@ Page({ myAuth: null }, + _calcTimer: null, + _calcSeq: 0, + onLoad(options) { const eventId = Number(options.eventId); this.setData({ eventId }); @@ -380,17 +428,34 @@ Page({ wx.showToast({ title: `该组别仅限${genderRequirementText(reqGender)}报名`, icon: 'none' }); return; } - const range = parseAgeRangeText(clickedGroup.ageRequirement); - if (range) { - const age = calcAgeYears(birthDate, eventStart); - if (age == null) { + // 年龄限制:优先支持“出生日期范围”,兼容旧“岁数范围” + const birthRange = parseBirthDateRangeText(clickedGroup.ageRequirement); + if (birthRange) { + const bd = parseYmdDateAny(birthDate); + if (!bd) { wx.showToast({ title: '无法解析出生日期,请先完善认证信息', icon: 'none' }); return; } - if (age < range.min || age > range.max) { - wx.showToast({ title: `年龄需在${range.min}-${range.max}岁`, icon: 'none' }); + 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; + } + } } } @@ -419,8 +484,65 @@ Page({ return; } - const totalPrice = calcTotalPrice(nextProjectGroups); - this.setData({ projectGroups: nextProjectGroups, totalPrice }); + 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 }); + return; + } + + const seq = (this._calcSeq += 1); + request({ + path: API.calculateAmount, + method: 'POST', + needAuth: true, + data: { + eventId: Number(this.data.eventId), + registrationMode: 2, + items + }, + contentType: 'application/json' + }) + .then((res) => { + // 兼容:后端可能返回 number / string / {amount} + if (seq !== this._calcSeq) return; + let amount = null; + if (typeof res === 'number') amount = res; + else if (typeof res === 'string') { + const n = Number(res); + amount = Number.isNaN(n) ? null : n; + } else if (res && typeof res === 'object') { + const v = res.amount != null ? res.amount : res.payAmount != null ? res.payAmount : res.totalAmount; + const n = Number(v); + amount = Number.isNaN(n) ? null : n; + } + if (amount == null) { + // fallback:仍然展示前端计算的费用,避免空白 + this.setData({ totalPrice: calcTotalPrice(this.data.projectGroups) }); + } else { + this.setData({ totalPrice: amount }); + } + }) + .catch(() => { + if (seq !== this._calcSeq) return; + this.setData({ totalPrice: calcTotalPrice(this.data.projectGroups) }); + }); }, onCouponInput(e) { diff --git a/utils/constants.js b/utils/constants.js index 61fa023..ab70cd1 100644 --- a/utils/constants.js +++ b/utils/constants.js @@ -9,6 +9,7 @@ const API = { eventMyList: '/api/eventclient/my-events', eventDetail: (id) => `/api/eventclient/${id}`, apply: '/api/eventclient/apply', + calculateAmount: '/api/eventclient/calculate-amount', wechatLogin: '/api/oauth/login', loginByCode: '/api/oauth/login', diff --git a/utils/http.js b/utils/http.js index 2cf3aff..ecbaffd 100644 --- a/utils/http.js +++ b/utils/http.js @@ -37,6 +37,7 @@ function getHeaderValue(headers, headerName) { function tryUpdateAccessTokenFromResponseHeaders(res) { // wx.request 的返回头在 res.header(部分环境也可能是 res.headers) + console.log( 'res', res); const headers = (res && res.header) || (res && res.headers) || null; const newAccessToken = getHeaderValue(headers, 'x-access-token'); if (!newAccessToken) return;