update:赛事详情&报名限制&openid

This commit is contained in:
ltx 2026-04-07 16:47:13 +08:00
parent 51791ffb17
commit c4faf65187
14 changed files with 557 additions and 39 deletions

View File

@ -1,6 +1,7 @@
const { API } = require('../../utils/constants');
const { request } = require('../../utils/http');
const { formatDateTime } = require('../../utils/format');
const auth = require('../../utils/auth');
// 中文数字映射(项目一、二、三…)
const CN_NUM = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
@ -9,6 +10,23 @@ 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 : [];
@ -25,7 +43,8 @@ function buildProjectGroups(projects) {
fee: g.fee != null ? g.fee : (p.fee != null ? p.fee : 0),
selected: false,
ageRequirement: g.ageRequirement || g.AgeRequirement || '',
genderRequirement: g.genderRequirement || g.GenderRequirement || '不限'
genderRequirement: g.genderRequirement != null ? g.genderRequirement : g.GenderRequirement != null ? g.GenderRequirement : '',
genderRequirementText: genderRequirementText(g.genderRequirement != null ? g.genderRequirement : g.GenderRequirement)
}))
};
});
@ -42,6 +61,107 @@ function calcTotalPrice(projectGroups) {
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 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 parseAgeRangeText(text) {
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 };
}
Page({
data: {
eventId: null,
@ -51,7 +171,9 @@ Page({
projectGroups: [], // 分组后的展示数据
couponCode: '',
totalPrice: 0,
submitting: false
submitting: false,
openidLoading: false,
myAuth: null
},
onLoad(options) {
@ -62,7 +184,9 @@ Page({
onShow() {
const accessToken = wx.getStorageSync('accessToken') || '';
this.setData({ loggedIn: Boolean(accessToken) });
const loggedIn = Boolean(accessToken);
this.setData({ loggedIn });
if (loggedIn) this._ensureMyAuth();
},
onBack() {
@ -81,7 +205,22 @@ Page({
needAuth: false
})
.then((detail) => {
const projects = Array.isArray(detail.projects) ? detail.projects : [];
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,
@ -95,14 +234,167 @@ Page({
});
},
_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;
const projectGroups = this.data.projectGroups.map((p) => {
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 range = parseAgeRangeText(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,
@ -112,8 +404,23 @@ Page({
})
};
});
const totalPrice = calcTotalPrice(projectGroups);
this.setData({ projectGroups, totalPrice });
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;
}
const totalPrice = calcTotalPrice(nextProjectGroups);
this.setData({ projectGroups: nextProjectGroups, totalPrice });
},
onCouponInput(e) {
@ -143,6 +450,24 @@ Page({
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,

View File

@ -1,5 +1,8 @@
<view class="apply-page">
<mp-navigation-bar title="报名" background-color="#ffffff" color="#333" bind:click-left="onBack" left-arrow></mp-navigation-bar>
<view class="openid-row">
<button class="openid-mini-btn" size="mini" bindtap="onFetchOpenId">获取openid</button>
</view>
<!-- 未登录 -->
<view wx:if="{{!loggedIn}}" class="not-login">
<text class="not-login-tip">登录后才能提交报名信息</text>
@ -21,7 +24,7 @@
<view wx:for="{{item.groups}}" wx:for-item="group" wx:key="groupId" class="group-tag {{group.selected ? 'selected' : ''}}" bindtap="onGroupTap" data-project-id="{{item.projectId}}" data-group-id="{{group.groupId}}">
{{group.groupName}}|
{{group.ageRequirement}}|
{{group.genderRequirement}}
{{group.genderRequirementText}}
<!-- {{group.ageRange || ''}}|{{group.gender || ''}} -->
</view>
</view>

View File

@ -4,6 +4,29 @@
background: #f5f7fa;
}
.openid-row {
display: flex;
justify-content: flex-end;
padding: 8rpx 32rpx 0;
box-sizing: border-box;
}
.openid-mini-btn {
margin: 0 !important;
padding: 0 20rpx !important;
height: 56rpx !important;
line-height: 56rpx !important;
font-size: 22rpx !important;
color: #1a56db !important;
background: rgba(26, 86, 219, 0.08) !important;
border: none !important;
border-radius: 28rpx !important;
}
.openid-mini-btn::after {
border: none;
}
.scroll {
height: calc(100vh - 88rpx);
padding: 24rpx 32rpx 0;

View File

@ -48,6 +48,49 @@ function pick(obj, keys) {
return null;
}
/**
* 赛事状态(与后端枚举一致)
* 0 NotStarted 未开始 | 1 Enrolling 报名中 | 2 RegistrationEnded 报名结束
* 3 Ongoing 比赛中 | 4 Ended 已结束
* 未返回字段时视为旧接口,不拦截报名
*/
function parseEventStatus(rawDetail) {
const v = pick(rawDetail, ['eventStatus', 'EventStatus']);
if (v == null || v === '') return null;
const n = Number(v);
return Number.isNaN(n) ? null : n;
}
function applyUiFromEventStatus(es) {
if (es == null) {
return { canApply: true, applyButtonText: '报名' };
}
if (es === 0) {
return { canApply: false, applyButtonText: '未开始' };
}
if (es === 1) {
return { canApply: true, applyButtonText: '报名' };
}
if (es === 2) {
return { canApply: false, applyButtonText: '报名结束' };
}
if (es === 3) {
return { canApply: false, applyButtonText: '比赛中' };
}
if (es === 4) {
return { canApply: false, applyButtonText: '已结束' };
}
return { canApply: false, applyButtonText: '暂不可报名' };
}
function toastForEventStatus(es) {
if (es === 0) return '赛事未开始';
if (es === 2) return '报名已结束';
if (es === 3) return '比赛进行中,暂不可报名';
if (es === 4) return '赛事已结束';
return '当前不可报名';
}
function registrationModeText(mode) {
const m = mode === undefined || mode === null ? null : Number(mode);
if (m === 1) return '只允许个人报名';
@ -73,6 +116,42 @@ function splitLines(text) {
.filter(Boolean);
}
function toNonNegInt(v) {
const n = Number(v);
if (Number.isNaN(n)) return null;
const i = Math.floor(n);
return i >= 0 ? i : null;
}
function buildRegistrationLimitLines(detail, projects) {
const d = detail && typeof detail === 'object' ? detail : {};
const lines = [];
const individualLimit = toNonNegInt(pick(d, ['individualProjectLimit', 'IndividualProjectLimit']));
const teamLimit = toNonNegInt(pick(d, ['teamProjectLimit', 'TeamProjectLimit']));
if (individualLimit != null || teamLimit != null) {
const a = individualLimit != null ? `个人项目${individualLimit}个` : null;
const b = teamLimit != null ? `团队项目${teamLimit}个` : null;
const joined = [a, b].filter(Boolean).join(',');
if (joined) lines.push(`每人/单位限报:${joined}`);
}
if (Array.isArray(projects) && projects.length) {
const segs = [];
projects.forEach((p) => {
const name = p && (p.name || p.Name) ? (p.name || p.Name) : '';
const limit = toNonNegInt(pick(p, ['projectLimitCount', 'ProjectLimitCount']));
if (!name || limit == null) return;
segs.push(`${name}${limit}个`);
});
if (segs.length) {
lines.push(`项目组别单位报名上限(每组别):${segs.join(',')}`);
}
}
return lines;
}
function flattenProjectTableRows(projects) {
if (!Array.isArray(projects)) return [];
const rows = [];
@ -179,6 +258,10 @@ function normalizeEventDetail(raw) {
registrationLimitLines = [String(registrationLimitRaw)];
}
}
// 若接口未提供文本规则,则由结构化字段拼接
if (!registrationLimitLines.length) {
registrationLimitLines = buildRegistrationLimitLines(d, projects);
}
const participationPointsLabel =
pick(d, ['participationPointsText', 'ParticipationPointsText']) ||
@ -236,7 +319,11 @@ Page({
hasNoticeBlock: false,
hasRegistrationLimit: false,
projectTableRows: [],
unitGroups: []
unitGroups: [],
/** 0未开始 1报名中 2报名结束 3比赛中 4已结束;null 表示接口未返回 */
eventStatus: null,
canApply: true,
applyButtonText: '报名'
},
onLoad(options) {
@ -353,6 +440,9 @@ Page({
.filter((t) => unitMap[t] && unitMap[t].length)
.map((t) => ({ label: UNIT_TYPE_LABELS[t], names: unitMap[t] }));
const eventStatus = parseEventStatus(detail);
const { canApply, applyButtonText } = applyUiFromEventStatus(eventStatus);
this.setData({
eventDetail: detail,
projects: normalizedProjects,
@ -378,7 +468,10 @@ Page({
hasNoticeBlock,
hasRegistrationLimit,
projectTableRows,
unitGroups
unitGroups,
eventStatus,
canApply,
applyButtonText
});
})
.catch(() => {
@ -389,6 +482,10 @@ Page({
onApplyTap() {
const id = this.data.id;
if (!id) return;
if (!this.data.canApply) {
wx.showToast({ title: toastForEventStatus(this.data.eventStatus), icon: 'none' });
return;
}
wx.navigateTo({
url: `/pages/apply/apply?eventId=${id}`
});

View File

@ -150,6 +150,11 @@
</view>
<view class="bottom-bar">
<button class="apply-btn" type="primary" bindtap="onApplyTap">报名</button>
<button
class="apply-btn {{canApply ? '' : 'apply-btn--disabled'}}"
type="primary"
disabled="{{!canApply}}"
bindtap="onApplyTap"
>{{applyButtonText}}</button>
</view>
</view>

View File

@ -516,3 +516,11 @@ page {
.apply-btn::after {
border: none;
}
.apply-btn.apply-btn--disabled,
.apply-btn[disabled] {
background: #c4c4c4 !important;
color: #ffffff !important;
box-shadow: none;
opacity: 1;
}

View File

@ -1,6 +1,6 @@
const { API } = require('../../utils/constants');
const { request } = require('../../utils/http');
const { formatDateTime, eventStatusText, eventFormatText } = require('../../utils/format');
const { formatDateTime, eventStatusText, eventStatusForPill, eventFormatText } = require('../../utils/format');
/** 统一后台轮播字段(camelCase / PascalCase、枚举数字)供 wxml 绑定 */
function normalizeBannerItem(raw) {
@ -54,6 +54,7 @@ Page({
pageSize: 10,
hasMore: true,
/** 列表筛选:-1=全部(不传 status),0~4 与后端 EventStatus 一致;默认报名中 */
status: 1,
keyword: '',
@ -136,6 +137,7 @@ Page({
onStatusTap(e) {
const status = Number(e.currentTarget.dataset.status);
if (Number.isNaN(status)) return;
this.setData({
status,
pageNo: 1,
@ -216,24 +218,36 @@ Page({
const status = this.data.status;
const keyword = this.data.keyword;
const query = {
page: pageNo,
pagesize: pageSize,
PageNo: pageNo,
PageSize: pageSize,
keyword,
Keyword: keyword
};
if (status >= 0) {
query.status = status;
query.Status = status;
}
return request({
path: API.eventList,
method: 'GET',
needAuth: false,
data: {
page: pageNo,
pagesize: pageSize,
PageNo: pageNo,
PageSize: pageSize,
status,
Status: status,
keyword,
Keyword: keyword
}
data: query
})
.then((res) => {
const rows = (res && Array.isArray(res.rows) ? res.rows : []).map((item) => {
const id = item.id;
const rawEventStatus =
item.eventStatus != null
? item.eventStatus
: item.EventStatus != null
? item.EventStatus
: item.status != null
? item.status
: item.Status;
return {
...item,
rowKey: `e-${id}`,
@ -241,7 +255,8 @@ Page({
registrationEndText: formatDateTime(item.registrationEndTime),
matchStartText: formatDateTime(item.matchStartTime),
matchEndText: formatDateTime(item.matchEndTime),
eventStatusText: eventStatusText(item.eventStatus),
eventStatusText: eventStatusText(rawEventStatus),
eventStatusPill: eventStatusForPill(rawEventStatus),
eventFormatText: eventFormatText(item.eventType || item.eventFormat)
};
});
@ -309,6 +324,7 @@ Page({
eventName: item.eventName,
coverImage: item.coverImage,
address: '',
eventStatusPill: 1,
eventStatusText: '已报名',
registrationTimeText: formatDateTime(item.registrationTime),
projectCount: item.projectCount,

View File

@ -80,13 +80,13 @@
bindtap="onStatusTap"
>报名中</view>
<view
class="status-pill {{status === 2 ? 'is-1' : ''}}"
data-status="2"
class="status-pill {{status === 3 ? 'is-1' : ''}}"
data-status="3"
bindtap="onStatusTap"
>比赛中</view>
<view
class="status-pill {{status === 3 ? 'is-2' : ''}}"
data-status="3"
class="status-pill {{status === 4 ? 'is-2' : ''}}"
data-status="4"
bindtap="onStatusTap"
>已结束</view>
</view>

View File

@ -281,13 +281,13 @@ page {
/* box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06); */
}
.status-pill-top-0 {
.status-pill-top-1 {
background: linear-gradient(135deg, rgba(0, 181, 109, 0) 20%, rgba(0, 96, 216, 1) 80% , rgba(0, 96, 216, 1) 100%) ;
}
.status-pill-top-1 {
.status-pill-top-3 {
background: linear-gradient(135deg, rgba(0, 181, 109, 0) 20%, rgba(0, 181, 109, 1) 80% , rgba(0, 181, 109, 1) 100%) ;
}
.status-pill-top-2 {
.status-pill-top-4 {
background: linear-gradient(135deg, rgba(0, 181, 109, 0) 20%, rgba(230, 6, 11, 1)80% , rgba(230, 6, 11, 1)100%) ;
}

View File

@ -94,6 +94,14 @@ Page({
// TODO: 上传头像到服务器
}
});
},
onFetchOpenId() {
wx.login({
success: (res) => {
console.log('res', res);
}
});
}
});

View File

@ -73,4 +73,8 @@
</view>
</view>
</view>
<view class="openid-row">
<button class="openid-mini-btn" size="mini" bindtap="onFetchOpenId">获取openid</button>
</view>
</view>

View File

@ -89,7 +89,7 @@ Page({
const passportImageUrl = d.passportImageUrl || '';
const birthDate = d.birthDate || d.birthday || '';
const gender = (d.gender || d.sex || '').toString().toUpperCase();
const genderIndex = gender === 'F' || gender === '女' || gender === '2' ? 1 : 0;
const genderIndex = gender === 'F' || gender === '女' || gender === '1' ? 1 : 0;
// 有已提交信息时,按产品稿表现为“自动识别/灰色不可修改”
const readonlyName = Boolean(name);

View File

@ -17,6 +17,8 @@ const API = {
oauthRegister: '/api/oauth/register',
oauthResetPassword: '/api/oauth/reset-password',
sendVerificationCode: '/api/sms/sendverificationcode',
/** 小程序 wx.login 的 code 换 openid(按后端实际路径修改) */
wechatOpenidByJsCode: '/api/wechat/jscode2session',
myProfile: '/api/my/profile',
myAuth: '/api/my/auth',
myRegistrations: '/api/my/registrations',

View File

@ -15,15 +15,41 @@ function formatTime(iso) {
}
function eventStatusText(status) {
if (status === 'Enrolling') return '报名中';
if (status === 'Ongoing') return '比赛中';
if (status === 'Ended') return '已结束';
const map = {
0: '报名中',
1: '比赛中',
2: '已结束'
// 与后端 EventStatus 枚举一致
const strMap = {
NotStarted: '未开始',
Enrolling: '报名中',
RegistrationEnded: '报名结束',
Ongoing: '比赛中',
Ended: '已结束'
};
return map[status] !== undefined ? map[status] : `状态${status}`;
if (strMap[status] != null) return strMap[status];
const n = status === undefined || status === null || status === '' ? NaN : Number(status);
const numMap = {
0: '未开始',
1: '报名中',
2: '报名结束',
3: '比赛中',
4: '已结束'
};
if (!Number.isNaN(n) && numMap[n] !== undefined) return numMap[n];
return status !== undefined && status !== null && status !== '' ? `状态${status}` : '';
}
/** 列表角标样式用:0~4,未知时回退 0 */
function eventStatusForPill(status) {
const strToNum = {
NotStarted: 0,
Enrolling: 1,
RegistrationEnded: 2,
Ongoing: 3,
Ended: 4
};
if (strToNum[status] !== undefined) return strToNum[status];
const n = Number(status);
if (!Number.isNaN(n) && n >= 0 && n <= 4) return n;
return 0;
}
function eventFormatText(v) {
@ -36,6 +62,7 @@ module.exports = {
formatDateTime,
formatTime,
eventStatusText,
eventStatusForPill,
eventFormatText
};