update:赛事详情&报名限制&openid
This commit is contained in:
parent
51791ffb17
commit
c4faf65187
@ -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,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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}`
|
||||
});
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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%) ;
|
||||
}
|
||||
|
||||
|
||||
@ -94,6 +94,14 @@ Page({
|
||||
// TODO: 上传头像到服务器
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onFetchOpenId() {
|
||||
wx.login({
|
||||
success: (res) => {
|
||||
console.log('res', res);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -73,4 +73,8 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="openid-row">
|
||||
<button class="openid-mini-btn" size="mini" bindtap="onFetchOpenId">获取openid</button>
|
||||
</view>
|
||||
</view>
|
||||
@ -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);
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user