803 lines
29 KiB
JavaScript
803 lines
29 KiB
JavaScript
const { API } = require('../../utils/constants');
|
||
const { request } = require('../../utils/http');
|
||
const { formatDateTime, eventFormatText } = require('../../utils/format');
|
||
|
||
/** 接口未返回时的 UI 兜底(与产品稿一致,有真实数据后自动以接口为准) */
|
||
var DETAIL_UI_FALLBACK = {
|
||
eventTitle: '2025-2026学年上海市中小学生跳绳锦标赛',
|
||
eventFormatTag: '线下',
|
||
registrationMethodText: '只允许个人报名',
|
||
feeModeLabel: '定价模式(赛事)',
|
||
priceDisplay: 892,
|
||
registrationStartText: '2015-10-02 09:21:00',
|
||
registrationEndText: '2015-10-10 18:00:00',
|
||
matchStartText: '2015-10-02 09:21:00',
|
||
matchEndText: '2015-10-10 18:00:00',
|
||
materialRangeText: '2015-10-02 09:21:00 ~ 2015-10-10 18:00:00',
|
||
uploadMaterialRemark:'',
|
||
eventAddress: '上海市杨浦区307号',
|
||
refundRulesText: '开赛前7天扣除金额5%,3-7天扣除金额10%,3天内扣除金额20%',
|
||
participationPointsLabel: '5积分',
|
||
rankingPointsLines: ['1--8 15积分', '9--20 10积分'],
|
||
registrationLimitLines: [
|
||
'每人限报个人项目2个,团队项目3个',
|
||
'项目-组别允许同一个个人/单位最大报名数量:30秒单摇2个,2*30秒双摇接力1个,30秒单摇2个'
|
||
],
|
||
introductionText:
|
||
'这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比.赛这是一场比赛这是一场比赛这是一场'
|
||
};
|
||
|
||
var DEMO_PROJECT_TABLE_ROWS = [
|
||
{ rowKey: 'fb-1', typeName: '个人', projectName: '30秒单摇', groupName: '少年组', fee: '23' },
|
||
{ rowKey: 'fb-2', typeName: '团队', projectName: '少年儿童组', groupName: '成年组', fee: '411' },
|
||
{ rowKey: 'fb-3', typeName: '团队', projectName: '少年儿童组', groupName: '32', fee: '32' },
|
||
{ rowKey: 'fb-4', typeName: '团队', projectName: '成年组', groupName: '12', fee: '12' }
|
||
];
|
||
|
||
var DEMO_PROJECT_LIMIT_TABLE_ROWS = [
|
||
{ rowKey: 'pl-1', projectName: '30秒单摇', groupName: '少年组', limitText: '2人/队' },
|
||
{ rowKey: 'pl-2', projectName: '2*30秒双摇接力', groupName: '少年组', limitText: '1人/队' },
|
||
{ rowKey: 'pl-3', projectName: '4*30秒交互绳接力', groupName: '青年组', limitText: '1人/队' }
|
||
];
|
||
|
||
function isEmptyDisplayTime(s) {
|
||
return s == null || String(s).trim() === '' || String(s).trim() === '-';
|
||
}
|
||
|
||
function pick(obj, keys) {
|
||
if (!obj) return null;
|
||
for (let i = 0; i < keys.length; i++) {
|
||
const v = obj[keys[i]];
|
||
if (v != null && v !== '') return v;
|
||
}
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* registrationType(产品定义)
|
||
* 0: 只允许团队报名
|
||
* 1: 只允许团队报名(需从用户后台报名,本端不可报名)
|
||
* 2: 个人/团队报名
|
||
*/
|
||
function parseRegistrationType(rawDetail) {
|
||
const v = pick(rawDetail, ['registrationType', 'RegistrationType']);
|
||
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 '只允许个人报名';
|
||
if (m === 2) return '只允许团队报名';
|
||
if (m === 0 || m === 3) return '个人与团队均可报名';
|
||
return null;
|
||
}
|
||
|
||
function registrationTypeText(regType) {
|
||
const m = regType === undefined || regType === null ? null : Number(regType);
|
||
if (m === 0) return '只允许个人报名';
|
||
if (m === 1) return '只允许团队报名(后台报名)';
|
||
if (m === 2) return '个人/团队报名';
|
||
return null;
|
||
}
|
||
|
||
function formatRange(start, end) {
|
||
const a = formatDateTime(start);
|
||
const b = formatDateTime(end);
|
||
if (a === '-' && b === '-') return '';
|
||
if (a === '-') return b;
|
||
if (b === '-') return a;
|
||
return `${a} ~ ${b}`;
|
||
}
|
||
|
||
function splitLines(text) {
|
||
if (!text || typeof text !== 'string') return [];
|
||
return text
|
||
.split(/\r?\n/)
|
||
.map((s) => s.trim())
|
||
.filter(Boolean);
|
||
}
|
||
|
||
function normalizePointsRuleDetail(raw) {
|
||
const d = raw && typeof raw === 'object' ? raw : null;
|
||
if (!d) return null;
|
||
const detailsRaw = Array.isArray(d.details) ? d.details : Array.isArray(d.Details) ? d.Details : [];
|
||
const details = detailsRaw
|
||
.map((x) => {
|
||
const startRank = toNonNegInt(pick(x, ['startRank', 'StartRank']));
|
||
const endRank = toNonNegInt(pick(x, ['endRank', 'EndRank']));
|
||
const points = toNonNegInt(pick(x, ['points', 'Points']));
|
||
if (startRank == null && endRank == null && points == null) return null;
|
||
return {
|
||
startRank: startRank != null ? startRank : endRank,
|
||
endRank: endRank != null ? endRank : startRank,
|
||
points
|
||
};
|
||
})
|
||
.filter(Boolean);
|
||
|
||
return {
|
||
ruleName: pick(d, ['ruleName', 'RuleName']) || '',
|
||
participationPoints: pick(d, ['participationPoints', 'ParticipationPoints']),
|
||
details
|
||
};
|
||
}
|
||
|
||
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, registrationType) {
|
||
const d = detail && typeof detail === 'object' ? detail : {};
|
||
const regType = registrationType == null ? null : Number(registrationType);
|
||
// regType === 0:只允许个人报名,隐藏团队限报
|
||
const isIndividualOnly = regType === 0;
|
||
const lines = [];
|
||
|
||
const individualLimit = toNonNegInt(pick(d, ['individualProjectLimit', 'IndividualProjectLimit']));
|
||
const teamLimit = isIndividualOnly ? null : 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 (!isIndividualOnly && 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 buildProjectLimitTableRows(projects) {
|
||
if (!Array.isArray(projects) || !projects.length) return [];
|
||
const rows = [];
|
||
projects.forEach((p, pi) => {
|
||
const projectName = p && (p.name || p.Name) ? (p.name || p.Name) : '-';
|
||
const projectLimit = toNonNegInt(pick(p, ['projectLimitCount', 'ProjectLimitCount']));
|
||
if (projectLimit == null) return;
|
||
|
||
const rawGroups = Array.isArray(p.groups) ? p.groups : Array.isArray(p.Groups) ? p.Groups : [];
|
||
if (!rawGroups.length) {
|
||
rows.push({
|
||
rowKey: `limit-${pi}-0`,
|
||
projectName,
|
||
groupName: '-',
|
||
limitText: `${projectLimit}人/队`
|
||
});
|
||
return;
|
||
}
|
||
|
||
rawGroups.forEach((g, gi) => {
|
||
const groupName = g && (g.groupName || g.GroupName || g.groupId || g.GroupId || '-');
|
||
rows.push({
|
||
rowKey: `limit-${pi}-${gi}`,
|
||
projectName,
|
||
groupName,
|
||
limitText: `${projectLimit}人/队`
|
||
});
|
||
});
|
||
});
|
||
return rows;
|
||
}
|
||
|
||
function flattenProjectTableRows(projects, chargeMode) {
|
||
if (!Array.isArray(projects)) return [];
|
||
const rows = [];
|
||
// chargeMode=3 单价(项目):费用挂在项目层,按项目一行,不展开组别
|
||
const byProject = chargeMode === 3;
|
||
projects.forEach((p) => {
|
||
const pid = p.id != null ? p.id : p.Id;
|
||
if (byProject) {
|
||
rows.push({
|
||
rowKey: `p-${pid}`,
|
||
typeName: p.typeName || p.TypeName || '-',
|
||
projectName: p.name || p.Name || '-',
|
||
groupName: '',
|
||
fee: p.fee != null ? p.fee : p.Fee != null ? p.Fee : '-'
|
||
});
|
||
return;
|
||
}
|
||
const rawGroups = Array.isArray(p.groups) ? p.groups : Array.isArray(p.Groups) ? p.Groups : [];
|
||
const groups = rawGroups.length ? rawGroups : [null];
|
||
groups.forEach((g, gi) => {
|
||
const gid = g && (g.groupId != null ? g.groupId : g.GroupId != null ? g.GroupId : null);
|
||
const gname = g && (g.groupName || g.GroupName);
|
||
rows.push({
|
||
rowKey: `p-${pid}-${gi}-${gid != null ? gid : gname || 'g'}`,
|
||
typeName: p.typeName || p.TypeName || '-',
|
||
projectName: p.name || p.Name || '-',
|
||
groupName: g ? gname || gid || '-' : '-',
|
||
fee: p.fee != null ? p.fee : p.Fee != null ? p.Fee : '-'
|
||
});
|
||
});
|
||
});
|
||
return rows;
|
||
}
|
||
|
||
function normalizeEventDetail(raw) {
|
||
const d = raw && typeof raw === 'object' ? raw : {};
|
||
const projects = Array.isArray(d.projects) ? d.projects : Array.isArray(d.Projects) ? d.Projects : [];
|
||
|
||
const registrationType = pick(d, ['registrationType', 'RegistrationType']);
|
||
const registrationMethodText =
|
||
pick(d, [
|
||
'registrationMethodText',
|
||
'RegistrationMethodText',
|
||
'registrationMethodDescription',
|
||
'RegistrationMethodDescription'
|
||
]) ||
|
||
registrationTypeText(registrationType) ||
|
||
registrationModeText(pick(d, ['registrationMode', 'RegistrationMode']));
|
||
|
||
const chargeModeRaw = pick(d, ['chargeMode', 'ChargeMode']);
|
||
const chargeMode = chargeModeRaw == null || chargeModeRaw === '' ? null : Number(chargeModeRaw);
|
||
const feeModeLabel =
|
||
chargeMode === 0
|
||
? '免费'
|
||
: chargeMode === 1
|
||
? '定价模式(赛事)'
|
||
: chargeMode === 2
|
||
? '单价(赛事)'
|
||
: '单价(项目)';
|
||
let priceDisplay = d && d.feeConfig ? d.feeConfig.price : null;
|
||
if (priceDisplay == null && projects.length) {
|
||
const fees = projects.map((p) => p.fee).filter((f) => f != null && f !== '');
|
||
if (fees.length) {
|
||
const nums = fees.map(Number).filter((n) => !Number.isNaN(n));
|
||
if (nums.length) priceDisplay = Math.min(...nums);
|
||
}
|
||
}
|
||
|
||
// chargeMode=3 单价(项目):价格展示固定为提示文案
|
||
let priceDisplayText = null;
|
||
if (chargeMode === 3) {
|
||
priceDisplayText = '价格见下方项目详情';
|
||
priceDisplay = null;
|
||
}
|
||
|
||
const materialRangeText =
|
||
pick(d, ['materialUploadDeadlineText', 'MaterialUploadDeadlineText']) ||
|
||
formatRange(
|
||
pick(d, ['materialUploadStartTime', 'MaterialUploadStartTime']),
|
||
pick(d, ['materialUploadEndTime', 'MaterialUploadEndTime'])
|
||
) ||
|
||
formatRange(
|
||
pick(d, ['uploadMaterialStartTime', 'UploadMaterialStartTime']),
|
||
pick(d, ['uploadMaterialEndTime', 'UploadMaterialEndTime'])
|
||
);
|
||
|
||
const refundRulesText = pick(d, ['refundRule', 'RefundRule', 'refundRules', 'RefundRules', 'refundDescription', 'RefundDescription']);
|
||
|
||
const pointsRuleDetailRaw = pick(d, ['pointsRuleDetail', 'PointsRuleDetail']);
|
||
const pointsRuleDetail = normalizePointsRuleDetail(pointsRuleDetailRaw);
|
||
|
||
const participationPoints = pointsRuleDetail
|
||
? pick(pointsRuleDetail, ['participationPoints', 'ParticipationPoints'])
|
||
: pick(d, ['participationPoints', 'ParticipationPoints']);
|
||
|
||
const rankingPointsRaw = pointsRuleDetail
|
||
? pointsRuleDetail.details
|
||
: pick(d, [
|
||
'rankingPointsText',
|
||
'RankingPointsText',
|
||
'rankingPointsDescription',
|
||
'RankingPointsDescription',
|
||
'rankingPoints',
|
||
'RankingPoints'
|
||
]);
|
||
let rankingPointsLines = [];
|
||
let rankingPointsTableRows = [];
|
||
if (Array.isArray(rankingPointsRaw) && rankingPointsRaw.length && typeof rankingPointsRaw[0] === 'object') {
|
||
rankingPointsTableRows = rankingPointsRaw
|
||
.map((it) => {
|
||
const sr = toNonNegInt(pick(it, ['startRank', 'StartRank']));
|
||
const er = toNonNegInt(pick(it, ['endRank', 'EndRank']));
|
||
const pts = toNonNegInt(pick(it, ['points', 'Points']));
|
||
if (pts == null || (sr == null && er == null)) return null;
|
||
const a = sr != null ? sr : er;
|
||
const b = er != null ? er : sr;
|
||
const rankText = a != null && b != null && a !== b ? `${a}-${b}` : `${a != null ? a : b}`;
|
||
return {
|
||
rowKey: `rank-${rankText}-${pts}`,
|
||
rankText,
|
||
pointsText: `${pts}积分`
|
||
};
|
||
})
|
||
.filter(Boolean);
|
||
rankingPointsLines = rankingPointsTableRows.map((row) => `${row.rankText} ${row.pointsText}`);
|
||
} else if (typeof rankingPointsRaw === 'string') {
|
||
rankingPointsLines = splitLines(rankingPointsRaw);
|
||
} else if (Array.isArray(rankingPointsRaw)) {
|
||
rankingPointsLines = rankingPointsRaw.map((x) => String(x)).filter(Boolean);
|
||
} else if (rankingPointsRaw != null && rankingPointsRaw !== '') {
|
||
rankingPointsLines = [String(rankingPointsRaw)];
|
||
}
|
||
|
||
if (!rankingPointsTableRows.length && rankingPointsLines.length) {
|
||
rankingPointsTableRows = rankingPointsLines.map((line, idx) => {
|
||
const txt = String(line || '').trim();
|
||
const m = txt.match(/(\d+(?:\s*[-~—]\s*\d+)?)\s*(.*)/);
|
||
if (!m) {
|
||
return {
|
||
rowKey: `rank-line-${idx}`,
|
||
rankText: txt || '-',
|
||
pointsText: '-'
|
||
};
|
||
}
|
||
return {
|
||
rowKey: `rank-line-${idx}`,
|
||
rankText: (m[1] || '-').replace(/[~—]/g, '-').replace(/\s+/g, ''),
|
||
pointsText: (m[2] || '').trim() || '-'
|
||
};
|
||
});
|
||
}
|
||
|
||
const registrationLimitRaw = pick(d, [
|
||
'registrationLimitText',
|
||
'RegistrationLimitText',
|
||
'registrationRules',
|
||
'RegistrationRules',
|
||
'limitDescription',
|
||
'LimitDescription'
|
||
]);
|
||
let registrationLimitLines = [];
|
||
if (registrationLimitRaw != null && registrationLimitRaw !== '') {
|
||
if (typeof registrationLimitRaw === 'string') {
|
||
registrationLimitLines = splitLines(registrationLimitRaw);
|
||
} else if (Array.isArray(registrationLimitRaw)) {
|
||
registrationLimitLines = registrationLimitRaw.filter((x) => x != null && String(x).trim() !== '');
|
||
} else {
|
||
registrationLimitLines = [String(registrationLimitRaw)];
|
||
}
|
||
}
|
||
// 若接口未提供文本规则,则由结构化字段拼接
|
||
if (!registrationLimitLines.length) {
|
||
registrationLimitLines = buildRegistrationLimitLines(d, projects, registrationType);
|
||
}
|
||
|
||
const individualProjectLimit = toNonNegInt(pick(d, ['individualProjectLimit', 'IndividualProjectLimit']));
|
||
const teamProjectLimit = toNonNegInt(pick(d, ['teamProjectLimit', 'TeamProjectLimit']));
|
||
const athleteLimitText =
|
||
`每位运动员可同时报名${individualProjectLimit != null ? individualProjectLimit : 'x'}个个人项目,` +
|
||
`${teamProjectLimit != null ? teamProjectLimit : 'x'}个团队项目`;
|
||
const projectLimitTableRows = buildProjectLimitTableRows(projects);
|
||
|
||
const participationPointsLabel =
|
||
pick(d, ['participationPointsText', 'ParticipationPointsText']) ||
|
||
(participationPoints != null && participationPoints !== ''
|
||
? `${participationPoints}积分`
|
||
: '');
|
||
|
||
const introImage = pick(d, ['introductionImage', 'IntroductionImage', 'detailImage', 'DetailImage']);
|
||
|
||
return {
|
||
detail: d,
|
||
projects,
|
||
eventFormatTag: eventFormatText(d.eventType || d.eventFormat || d.EventType || d.EventFormat),
|
||
registrationMethodText,
|
||
feeModeLabel: feeModeLabel || '定价模式(赛事)',
|
||
priceDisplay,
|
||
priceDisplayText,
|
||
materialRangeText,
|
||
refundRulesText,
|
||
participationPointsLabel,
|
||
rankingPointsLines,
|
||
rankingPointsTableRows,
|
||
registrationLimitLines,
|
||
athleteLimitText,
|
||
projectLimitTableRows,
|
||
introImage,
|
||
hasNoticeBlock: Boolean(refundRulesText || participationPointsLabel || rankingPointsLines.length),
|
||
hasRegistrationLimit: registrationLimitLines.length > 0 || projectLimitTableRows.length > 0,
|
||
chargeMode,
|
||
projectTableRows: flattenProjectTableRows(projects, chargeMode)
|
||
};
|
||
}
|
||
|
||
Page({
|
||
data: {
|
||
id: null,
|
||
eventDetail: {},
|
||
projects: [],
|
||
|
||
registrationStartText: '',
|
||
registrationEndText: '',
|
||
matchStartText: '',
|
||
matchEndText: '',
|
||
|
||
eventFormatTag: '',
|
||
registrationMethodText: '',
|
||
feeModeLabel: '',
|
||
priceDisplay: null,
|
||
priceDisplayText: '',
|
||
materialRangeText: '',
|
||
refundRulesText: '',
|
||
participationPointsLabel: '',
|
||
rankingPointsLines: [],
|
||
rankingPointsTableRows: [],
|
||
registrationLimitLines: [],
|
||
athleteLimitText: '',
|
||
projectLimitTableRows: [],
|
||
introImage: '',
|
||
introductionText: '',
|
||
uploadMaterialRemark: '',
|
||
eventTitle: '',
|
||
eventAddress: '',
|
||
heroImage: '',
|
||
hasNoticeBlock: false,
|
||
hasRegistrationLimit: false,
|
||
projectTableRows: [],
|
||
unitGroups: [],
|
||
/** 0未开始 1报名中 2报名结束 3比赛中 4已结束;null 表示接口未返回 */
|
||
eventStatus: null,
|
||
registrationType: null,
|
||
/** 是否显示上传素材截止时间(isNeedMaterial=false 时隐藏) */
|
||
showMaterialRange: true,
|
||
/** 是否灰态展示(不等同于 disabled) */
|
||
applyGrey: false,
|
||
/** 是否原生禁用(禁用后无法点击) */
|
||
applyDisabled: false,
|
||
/** 是否被“需后台报名”规则拦截(按钮灰但可点,点后弹窗引导) */
|
||
applyBlockedByAdmin: false,
|
||
applyButtonText: '报名',
|
||
/** 当前用户是否已报名该赛事 */
|
||
isRegistered: false,
|
||
/** 收费模式 0免费 1定价赛事 2单价赛事 3单价项目 */
|
||
chargeMode: null
|
||
},
|
||
|
||
onLoad(options) {
|
||
console.log('Detail onLoad options:', options);
|
||
|
||
let rawId = options.id;
|
||
|
||
// 处理扫描普通二维码进入:options.q 为 URL 编码后的链接
|
||
// 格式:https://jitsi-web.yuedongsports.com/mini-app/event?id=xxx
|
||
if (!rawId && options.q) {
|
||
try {
|
||
const decoded = decodeURIComponent(options.q);
|
||
const match = decoded.match(/[?&]id=([^&]+)/);
|
||
console.log('Detail onLoad options:', decoded);
|
||
console.log('Detail onLoad options:', match);
|
||
|
||
if (match) rawId = match[1];
|
||
} catch (e) {
|
||
console.warn('Detail: 解析 options.q 失败', e);
|
||
}
|
||
}
|
||
|
||
const id = Number(rawId);
|
||
this.setData({ id });
|
||
if (!id) return;
|
||
this.fetchDetail(id);
|
||
},
|
||
|
||
fetchDetail(id) {
|
||
request({
|
||
path: API.eventDetail(id),
|
||
method: 'GET',
|
||
needAuth: true
|
||
})
|
||
.then((res) => {
|
||
const detail = res || {};
|
||
const projects = Array.isArray(detail.projects) ? detail.projects : [];
|
||
const normalizedProjects = projects.map((p) => {
|
||
const groups = Array.isArray(p.groups)
|
||
? p.groups
|
||
: Array.isArray(p.Groups)
|
||
? p.Groups
|
||
: [];
|
||
const groupSummary = groups.length
|
||
? groups
|
||
.map((g) => g.groupName || g.groupId)
|
||
.filter(Boolean)
|
||
.join(',')
|
||
: '';
|
||
return { ...p, groupSummary };
|
||
});
|
||
|
||
const n = normalizeEventDetail({ ...detail, projects: normalizedProjects });
|
||
const fb = DETAIL_UI_FALLBACK;
|
||
|
||
var introductionText =
|
||
pick(detail, ['eventIntroduction', 'EventIntroduction', 'introduction', 'Introduction']) || '';
|
||
if (!introductionText.trim()) introductionText = fb.introductionText;
|
||
|
||
var eventTitle = pick(detail, ['eventName', 'EventName']) || '';
|
||
if (!eventTitle.trim()) eventTitle = fb.eventTitle;
|
||
|
||
var eventAddress = pick(detail, ['address', 'Address']) || '';
|
||
if (!eventAddress.trim()) eventAddress = fb.eventAddress;
|
||
|
||
var heroImage = pick(detail, ['coverImage', 'CoverImage']) || '';
|
||
|
||
var registrationStartText = formatDateTime(
|
||
pick(detail, ['registrationStartTime', 'RegistrationStartTime'])
|
||
);
|
||
var registrationEndText = formatDateTime(
|
||
pick(detail, ['registrationEndTime', 'RegistrationEndTime'])
|
||
);
|
||
if (isEmptyDisplayTime(registrationStartText)) registrationStartText = fb.registrationStartText;
|
||
if (isEmptyDisplayTime(registrationEndText)) registrationEndText = fb.registrationEndText;
|
||
|
||
var matchStartText = formatDateTime(pick(detail, ['matchStartTime', 'MatchStartTime']));
|
||
var matchEndText = formatDateTime(pick(detail, ['matchEndTime', 'MatchEndTime']));
|
||
if (isEmptyDisplayTime(matchStartText)) matchStartText = fb.matchStartText;
|
||
if (isEmptyDisplayTime(matchEndText)) matchEndText = fb.matchEndText;
|
||
|
||
var eventFormatTag = n.eventFormatTag || fb.eventFormatTag;
|
||
var registrationMethodText = n.registrationMethodText || fb.registrationMethodText;
|
||
var feeModeLabel = n.feeModeLabel || fb.feeModeLabel;
|
||
var priceDisplay = n.priceDisplay != null ? n.priceDisplay : fb.priceDisplay;
|
||
var priceDisplayText = n.priceDisplayText || '';
|
||
|
||
var materialRangeText = n.materialRangeText || fb.materialRangeText;
|
||
|
||
var uploadMaterialRemark =
|
||
pick(detail, [
|
||
'uploadMaterialRemark',
|
||
'UploadMaterialRemark',
|
||
'materialRemark',
|
||
'MaterialRemark'
|
||
]) || '';
|
||
if (!uploadMaterialRemark.trim()) uploadMaterialRemark = fb.uploadMaterialRemark;
|
||
|
||
var refundRulesText = n.refundRulesText || fb.refundRulesText;
|
||
var participationPointsLabel = n.participationPointsLabel || fb.participationPointsLabel;
|
||
var rankingPointsLines =
|
||
n.rankingPointsLines && n.rankingPointsLines.length ? n.rankingPointsLines : fb.rankingPointsLines;
|
||
var rankingPointsTableRows =
|
||
n.rankingPointsTableRows && n.rankingPointsTableRows.length
|
||
? n.rankingPointsTableRows
|
||
: [
|
||
{ rowKey: 'fb-rank-1', rankText: '1-8', pointsText: '15积分' },
|
||
{ rowKey: 'fb-rank-2', rankText: '9-20', pointsText: '10积分' }
|
||
];
|
||
var registrationLimitLines =
|
||
n.registrationLimitLines && n.registrationLimitLines.length
|
||
? n.registrationLimitLines
|
||
: fb.registrationLimitLines;
|
||
var athleteLimitText = n.athleteLimitText || '每位运动员可同时报名x个个人项目,x个团队项目';
|
||
var projectLimitTableRows =
|
||
n.projectLimitTableRows && n.projectLimitTableRows.length
|
||
? n.projectLimitTableRows
|
||
: DEMO_PROJECT_LIMIT_TABLE_ROWS;
|
||
|
||
var projectTableRows =
|
||
n.projectTableRows && n.projectTableRows.length ? n.projectTableRows : DEMO_PROJECT_TABLE_ROWS;
|
||
var chargeMode = n.chargeMode != null ? n.chargeMode : null;
|
||
|
||
var hasNoticeBlock = Boolean(
|
||
refundRulesText || participationPointsLabel || rankingPointsLines.length
|
||
);
|
||
var hasRegistrationLimit = registrationLimitLines.length > 0;
|
||
if (projectLimitTableRows.length) {
|
||
hasRegistrationLimit = true;
|
||
}
|
||
|
||
// 解析 units 数组,保持后端原始顺序并分组
|
||
const UNIT_TYPE_LABELS = ['主办单位', '承办单位', '协办单位', '赞助单位'];
|
||
const rawUnits = Array.isArray(detail.units)
|
||
? detail.units
|
||
: Array.isArray(detail.Units)
|
||
? detail.Units
|
||
: [];
|
||
|
||
const unitGroups = [];
|
||
const labelMap = new Map(); // 用于记录标签在 unitGroups 中的索引位置
|
||
|
||
rawUnits.forEach((u) => {
|
||
const customType = u.customUnitType || u.CustomUnitType;
|
||
const type = u.unitType != null ? u.unitType : u.UnitType != null ? u.UnitType : null;
|
||
const name = u.unitName || u.UnitName || u.name || u.Name || '';
|
||
|
||
if (!name.trim()) return;
|
||
|
||
// 确定分组标签
|
||
let label;
|
||
if (customType && customType.trim()) {
|
||
label = customType.trim();
|
||
} else if (type != null) {
|
||
const t = Number(type);
|
||
label = UNIT_TYPE_LABELS[t];
|
||
} else {
|
||
return;
|
||
}
|
||
|
||
// 查找或创建分组
|
||
if (labelMap.has(label)) {
|
||
// 分组已存在,添加到该分组
|
||
const index = labelMap.get(label);
|
||
unitGroups[index].names.push(name.trim());
|
||
} else {
|
||
// 新分组,添加到数组并记录索引
|
||
labelMap.set(label, unitGroups.length);
|
||
unitGroups.push({ label, names: [name.trim()] });
|
||
}
|
||
});
|
||
|
||
const eventStatus = parseEventStatus(detail);
|
||
const { canApply, applyButtonText } = applyUiFromEventStatus(eventStatus);
|
||
const registrationType = parseRegistrationType(detail);
|
||
|
||
// isNeedMaterial:接口未返回时默认显示;明确为 false/0 时隐藏
|
||
const isNeedMaterialRaw = pick(detail, ['isNeedMaterial', 'IsNeedMaterial']);
|
||
const showMaterialRange =
|
||
isNeedMaterialRaw == null ||
|
||
(isNeedMaterialRaw !== false &&
|
||
isNeedMaterialRaw !== 0 &&
|
||
isNeedMaterialRaw !== '0' &&
|
||
isNeedMaterialRaw !== 'false');
|
||
|
||
// 报名按钮逻辑:eventStatus 先拦截;再按 registrationType 决定是否可在小程序端报名
|
||
let applyDisabled = !canApply; // 原生禁用:不可点
|
||
let applyGrey = !canApply; // 灰态:视觉置灰
|
||
let applyBlockedByAdmin = false;
|
||
let finalApplyButtonText = applyButtonText;
|
||
|
||
// 只有 registrationType 为 0 或 2 才允许报名;为 1 时灰态但可点,点击引导去后台
|
||
if (canApply) {
|
||
if (registrationType === 1) {
|
||
applyDisabled = false;
|
||
applyGrey = true;
|
||
applyBlockedByAdmin = true;
|
||
finalApplyButtonText = '报名';
|
||
} else if (registrationType === 0 || registrationType === 2 || registrationType == null) {
|
||
applyDisabled = false;
|
||
applyGrey = false;
|
||
applyBlockedByAdmin = false;
|
||
finalApplyButtonText = '报名';
|
||
} else {
|
||
// 未知值:保守拦截
|
||
applyDisabled = true;
|
||
applyGrey = true;
|
||
applyBlockedByAdmin = false;
|
||
finalApplyButtonText = '暂不可报名';
|
||
}
|
||
}
|
||
|
||
const isRegisteredRaw = pick(detail, ['isRegistered', 'IsRegistered']);
|
||
const isRegistered = isRegisteredRaw === true || isRegisteredRaw === 1 || isRegisteredRaw === 'true';
|
||
|
||
this.setData({
|
||
eventDetail: detail,
|
||
projects: normalizedProjects,
|
||
introductionText,
|
||
eventTitle,
|
||
eventAddress,
|
||
heroImage,
|
||
registrationStartText,
|
||
registrationEndText,
|
||
matchStartText,
|
||
matchEndText,
|
||
eventFormatTag,
|
||
registrationMethodText,
|
||
feeModeLabel,
|
||
priceDisplay,
|
||
priceDisplayText,
|
||
materialRangeText,
|
||
uploadMaterialRemark,
|
||
refundRulesText,
|
||
participationPointsLabel,
|
||
rankingPointsLines,
|
||
rankingPointsTableRows,
|
||
registrationLimitLines,
|
||
athleteLimitText,
|
||
projectLimitTableRows,
|
||
introImage: n.introImage || '',
|
||
hasNoticeBlock,
|
||
hasRegistrationLimit,
|
||
projectTableRows,
|
||
unitGroups,
|
||
eventStatus,
|
||
registrationType,
|
||
showMaterialRange,
|
||
chargeMode,
|
||
applyGrey,
|
||
applyDisabled,
|
||
applyBlockedByAdmin,
|
||
applyButtonText: finalApplyButtonText,
|
||
isRegistered
|
||
});
|
||
})
|
||
.catch((err) => {
|
||
// wx.showToast({ title: err.Error, icon: 'none' });
|
||
// console.log('Detail fetchDetail error:', err);
|
||
this.setData({
|
||
applyGrey: true,
|
||
applyDisabled: true,
|
||
applyBlockedByAdmin: false,
|
||
applyButtonText: '暂不可报名'
|
||
});
|
||
});
|
||
},
|
||
|
||
onApplyTap() {
|
||
const id = this.data.id;
|
||
if (!id) return;
|
||
if (this.data.isRegistered) {
|
||
wx.showModal({
|
||
title: '提示',
|
||
content: '您已报名该赛事,请先取消已报名的项目后再重新报名',
|
||
showCancel: false,
|
||
confirmText: '我知道了'
|
||
});
|
||
return;
|
||
}
|
||
if (this.data.applyBlockedByAdmin) {
|
||
wx.showModal({
|
||
title: '提示',
|
||
content: '该赛事需从用户后台进行报名',
|
||
showCancel: false,
|
||
confirmText: '我知道了'
|
||
});
|
||
return;
|
||
}
|
||
if (this.data.applyDisabled) {
|
||
wx.showToast({ title: toastForEventStatus(this.data.eventStatus), icon: 'none' });
|
||
return;
|
||
}
|
||
wx.navigateTo({
|
||
url: `/pages/apply/apply?eventId=${id}`
|
||
});
|
||
}
|
||
});
|