update:赛事详情

This commit is contained in:
ltx 2026-04-01 18:00:40 +08:00
parent 738fe18086
commit fa110f6a6a
7 changed files with 988 additions and 135 deletions

View File

@ -1,6 +1,213 @@
const { API } = require('../../utils/constants');
const { request } = require('../../utils/http');
const { formatDateTime, eventStatusText } = require('../../utils/format');
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:
'这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比赛这是一场比.赛这是一场比赛这是一场比赛这是一场',
organizerText: '上海市杨浦区闸殷路307号',
coOrganizerText: '上海市杨浦区闸殷路307号'
};
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' }
];
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;
}
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 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 flattenProjectTableRows(projects) {
if (!Array.isArray(projects)) return [];
const rows = [];
projects.forEach((p) => {
const pid = p.id != null ? p.id : p.Id;
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 registrationMethodText =
pick(d, [
'registrationMethodText',
'RegistrationMethodText',
'registrationMethodDescription',
'RegistrationMethodDescription'
]) || registrationModeText(pick(d, ['registrationMode', 'RegistrationMode']));
const feeModeLabel =
pick(d, ['feeModeText', 'FeeModeText', 'feeModeDescription', 'FeeModeDescription', 'pricingModeText', 'PricingModeText']) ||
pick(d, ['feeMode', 'FeeMode', 'pricingMode', 'PricingMode']) ||
'';
const priceRaw = pick(d, [
'displayPrice',
'DisplayPrice',
'eventPrice',
'EventPrice',
'price',
'Price',
'minFee',
'MinFee'
]);
let priceDisplay = priceRaw != null && priceRaw !== '' ? priceRaw : 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);
}
}
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, ['refundRules', 'RefundRules', 'refundDescription', 'RefundDescription']);
const participationPoints = pick(d, ['participationPoints', 'ParticipationPoints']);
const rankingPointsRaw = pick(d, [
'rankingPointsText',
'RankingPointsText',
'rankingPointsDescription',
'RankingPointsDescription',
'rankingPoints',
'RankingPoints'
]);
let rankingPointsLines = [];
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)];
}
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)];
}
}
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,
materialRangeText,
refundRulesText,
participationPointsLabel,
rankingPointsLines,
registrationLimitLines,
introImage,
hasNoticeBlock: Boolean(refundRulesText || participationPointsLabel || rankingPointsLines.length),
hasRegistrationLimit: registrationLimitLines.length > 0,
projectTableRows: flattenProjectTableRows(projects)
};
}
Page({
data: {
@ -8,11 +215,31 @@ Page({
eventDetail: {},
projects: [],
eventStatusText: '',
registrationStartText: '',
registrationEndText: '',
matchStartText: '',
matchEndText: ''
matchEndText: '',
eventFormatTag: '',
registrationMethodText: '',
feeModeLabel: '',
priceDisplay: null,
materialRangeText: '',
refundRulesText: '',
participationPointsLabel: '',
rankingPointsLines: [],
registrationLimitLines: [],
introImage: '',
introductionText: '',
uploadMaterialRemark: '',
eventTitle: '',
eventAddress: '',
heroImage: '',
hasNoticeBlock: false,
hasRegistrationLimit: false,
projectTableRows: [],
organizerText: '',
coOrganizerText: ''
},
onLoad(options) {
@ -31,30 +258,136 @@ Page({
.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 : [];
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
};
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 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 registrationLimitLines =
n.registrationLimitLines && n.registrationLimitLines.length
? n.registrationLimitLines
: fb.registrationLimitLines;
var projectTableRows =
n.projectTableRows && n.projectTableRows.length ? n.projectTableRows : DEMO_PROJECT_TABLE_ROWS;
var hasNoticeBlock = Boolean(
refundRulesText || participationPointsLabel || rankingPointsLines.length
);
var hasRegistrationLimit = registrationLimitLines.length > 0;
var organizerText =
pick(detail, [
'organizer',
'Organizer',
'organizerText',
'OrganizerText',
'hostUnit',
'HostUnit',
'sponsor',
'Sponsor'
]) || '';
if (!String(organizerText).trim()) organizerText = fb.organizerText;
var coOrganizerText =
pick(detail, [
'coOrganizer',
'CoOrganizer',
'coOrganizerText',
'CoOrganizerText',
'assistantUnit',
'AssistantUnit',
'coSponsor',
'CoSponsor'
]) || '';
if (!String(coOrganizerText).trim()) coOrganizerText = fb.coOrganizerText;
this.setData({
eventDetail: detail,
projects: normalizedProjects,
eventStatusText: eventStatusText(detail.eventStatus),
registrationStartText: formatDateTime(detail.registrationStartTime),
registrationEndText: formatDateTime(detail.registrationEndTime),
matchStartText: formatDateTime(detail.matchStartTime),
matchEndText: formatDateTime(detail.matchEndTime)
introductionText,
eventTitle,
eventAddress,
heroImage,
registrationStartText,
registrationEndText,
matchStartText,
matchEndText,
eventFormatTag,
registrationMethodText,
feeModeLabel,
priceDisplay,
materialRangeText,
uploadMaterialRemark,
refundRulesText,
participationPointsLabel,
rankingPointsLines,
registrationLimitLines,
introImage: n.introImage || '',
hasNoticeBlock,
hasRegistrationLimit,
projectTableRows,
organizerText,
coOrganizerText
});
})
.catch(() => {
@ -70,4 +403,3 @@ Page({
});
}
});

6
pages/detail/detail.json Normal file
View File

@ -0,0 +1,6 @@
{
"navigationBarTitleText": "赛事详情",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#f7f9fa"
}

View File

@ -1,57 +1,151 @@
<view class="container">
<image class="cover" src="{{eventDetail.coverImage}}" mode="aspectFill" />
<view class="page-detail">
<image wx:if="{{heroImage}}" class="detail-hero" src="{{heroImage}}" mode="aspectFill" />
<view wx:else class="detail-hero detail-hero--placeholder" />
<view class="content">
<view class="title-row">
<view class="title">{{eventDetail.eventName || '-'}}</view>
<view class="status">{{eventStatusText}}</view>
<view class="main-sheet">
<view class="title-block">
<view class="title-row">
<view class="format-tag" wx:if="{{eventFormatTag}}">{{eventFormatTag}}</view>
<view class="event-title">{{eventTitle || '—'}}</view>
</view>
</view>
<view class="meta" wx:if="{{eventDetail.address}}">
<text>地址:</text>
<text>{{eventDetail.address}}</text>
</view>
<view class="meta">
<text class="label">报名:</text>
<text>{{registrationStartText}} - {{registrationEndText}}</text>
</view>
<view class="meta">
<text class="label">比赛:</text>
<text>{{matchStartText}} - {{matchEndText}}</text>
</view>
<view class="section" wx:if="{{eventDetail.eventIntroduction}}">
<view class="section-title">赛事介绍</view>
<view class="intro">{{eventDetail.eventIntroduction}}</view>
</view>
<view class="section">
<view class="section-title">赛事项目</view>
<block wx:for="{{projects}}" wx:key="id">
<view class="project-card">
<view class="pname">{{item.name || '-'}}</view>
<view class="pinfo">
<text>类型:</text>
<text>{{item.typeName || '-'}}</text>
</view>
<view class="pinfo">
<text>费用:</text>
<text>{{item.fee != null ? item.fee : '-'}}</text>
<view class="info-list">
<view class="info-row">
<text class="info-label">报名方式:</text>
<text class="info-value">{{registrationMethodText || '—'}}</text>
</view>
<view class="info-row">
<text class="info-label">收费模式:</text>
<view class="info-value info-value--fee">
<text class="info-value-txt">{{feeModeLabel}}</text>
<view wx:if="{{priceDisplay != null}}" class="fee-inline">
<text class="fee-currency">¥</text>
<text class="fee-digits">{{priceDisplay}}</text>
</view>
<view class="info-i" wx:if="{{priceDisplay != null}}">i</view>
</view>
</view>
<view class="info-row">
<text class="info-label">报名时间:</text>
<text class="info-value info-value--wrap">{{registrationStartText}}~{{registrationEndText}}</text>
</view>
<view class="info-row" wx:if="{{materialRangeText}}">
<text class="info-label">上传素材截止时间:</text>
<text class="info-value info-value--wrap">{{materialRangeText}}</text>
</view>
<view class="info-row">
<text class="info-label">比赛时间:</text>
<text class="info-value info-value--wrap">{{matchStartText}}~{{matchEndText}}</text>
</view>
<view class="info-row" wx:if="{{uploadMaterialRemark}}">
<text class="info-label">上传素材备注:</text>
<text class="info-value info-value--wrap">{{uploadMaterialRemark}}</text>
</view>
<view class="info-row" wx:if="{{eventAddress}}">
<text class="info-label">赛事地址:</text>
<text class="info-value info-value--wrap">{{eventAddress}}</text>
</view>
</view>
<view class="groups" wx:if="{{item.groupSummary}}">
<text>组别:</text>
<text>{{item.groupSummary}}</text>
<view class="notice-card" wx:if="{{hasNoticeBlock}}">
<view class="card-head card-head--notice">
<view class="card-icon card-icon--speaker">📢</view>
<text class="card-head-title">赛事须知</text>
</view>
<view class="notice-body">
<view class="notice-p" wx:if="{{refundRulesText}}">
<text class="notice-strong">退款规则:</text>
<text class="notice-text">{{refundRulesText}}</text>
</view>
<view class="notice-row" wx:if="{{participationPointsLabel}}">
<text class="notice-strong">参与积分:</text>
<text class="notice-text">{{participationPointsLabel}}</text>
</view>
<view class="notice-ranking" wx:if="{{rankingPointsLines.length}}">
<text class="notice-strong">名次积分:</text>
<view class="ranking-lines">
<text class="notice-text ranking-line" wx:for="{{rankingPointsLines}}" wx:for-index="ri" wx:key="ri">{{item}}</text>
</view>
</view>
</block>
</view>
</view>
<view class="section-with-bullet" wx:if="{{hasRegistrationLimit || projectTableRows.length}}">
<view class="section-head">
<view class="section-bullet" />
<text class="section-title">限报与项目</text>
</view>
<view class="section-stack">
<view class="limit-card" wx:if="{{hasRegistrationLimit}}">
<view class="card-head card-head--limit">
<view class="card-icon card-icon--speaker">📢</view>
<text class="card-head-title">报名限制</text>
</view>
<view class="limit-list">
<view class="limit-item" wx:for="{{registrationLimitLines}}" wx:for-index="idx" wx:key="idx">
<text class="limit-num">({{idx + 1}})</text>
<text class="limit-text">{{item}}</text>
</view>
</view>
</view>
<view class="project-panel" wx:if="{{projectTableRows.length}}">
<view class="card-head card-head--project">
<view class="card-icon card-icon--doc">📋</view>
<text class="card-head-title">项目列表</text>
</view>
<view class="table-wrap">
<view class="table-head">
<text class="th th-type">类型</text>
<text class="th th-proj">项目</text>
<text class="th th-group">组别</text>
<text class="th th-fee">费用(元)</text>
</view>
<view class="table-body">
<view class="tr" wx:for="{{projectTableRows}}" wx:key="rowKey">
<text class="td td-type">{{item.typeName}}</text>
<text class="td td-proj">{{item.projectName}}</text>
<text class="td td-group">{{item.groupName}}</text>
<text class="td td-fee">{{item.fee}}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="section-with-bullet" wx:if="{{introductionText || introImage}}">
<view class="section-head">
<view class="section-bullet" />
<text class="section-title">赛事简介</text>
</view>
<view class="intro-block">
<text class="intro-text" wx:if="{{introductionText}}">{{introductionText}}</text>
<image wx:if="{{introImage}}" class="intro-image" src="{{introImage}}" mode="widthFix" />
</view>
</view>
<view class="participant-section" wx:if="{{organizerText || coOrganizerText}}">
<view class="participant-head">
<view class="participant-hex" />
<text class="participant-title">参与单位</text>
</view>
<view class="participant-card">
<view class="participant-row" wx:if="{{organizerText}}">
<text class="participant-label">主办方:</text>
<text class="participant-value">{{organizerText}}</text>
</view>
<view class="participant-row" wx:if="{{coOrganizerText}}">
<text class="participant-label">协办单位:</text>
<text class="participant-value">{{coOrganizerText}}</text>
</view>
</view>
</view>
</view>
<view class="bottom-bar">
<button class="apply-btn" type="primary" bindtap="onApplyTap">报名该赛事</button>
<button class="apply-btn" type="primary" bindtap="onApplyTap">报名</button>
</view>
</view>

View File

@ -1,119 +1,499 @@
.container {
/* Pixso 112:1637:主色 #1A57B2,页 #F7F9FA,卡片 #F7F8FD */
page {
background: #f7f9fa;
color: #333333;
}
.page-detail {
min-height: 100vh;
background: #f7f8fa;
box-sizing: border-box;
padding-bottom: calc(168rpx + env(safe-area-inset-bottom));
}
.cover {
.detail-hero {
width: 100%;
height: 320rpx;
background: #eaeaea;
height: 460rpx;
display: block;
background: #e8e8e8;
}
.content {
padding: 18rpx 16rpx 140rpx;
.detail-hero--placeholder {
box-sizing: border-box;
}
.main-sheet {
position: relative;
z-index: 1;
margin-top: -48rpx;
padding: 30rpx 40rpx 40rpx;
background: #ffffff;
border-radius: 40rpx 40rpx 16rpx 16rpx;
box-shadow: 0 -12rpx 40rpx rgba(0, 0, 0, 0.06);
}
.title-block {
margin-bottom: 8rpx;
}
.title-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12rpx;
margin-top: -54rpx;
gap: 16rpx;
}
.title {
flex: 1;
background: rgba(255, 255, 255, 0.92);
padding: 18rpx 18rpx;
border-radius: 18rpx;
font-size: 32rpx;
font-weight: 900;
color: #222;
}
.status {
background: rgba(26, 173, 25, 0.10);
color: #1aad19;
border-radius: 999rpx;
padding: 12rpx 18rpx;
font-size: 24rpx;
.format-tag {
flex-shrink: 0;
margin-top: 8rpx;
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 28rpx;
padding: 4rpx 12rpx;
font-size: 22rpx;
font-weight: 700;
white-space: nowrap;
color: #ffffff;
background: #1a57b2;
border-radius: 8rpx;
}
.meta {
margin-top: 16rpx;
.event-title {
flex: 1;
min-width: 0;
font-size: 28rpx;
font-weight: 700;
line-height: 1.5;
color: #000000;
}
.info-list {
margin-top: 24rpx;
}
.info-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24rpx;
padding: 22rpx 0;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
flex-shrink: 0;
width: 220rpx;
font-size: 24rpx;
color: rgba(0, 0, 0, 0.6);
line-height: 1.55;
}
.info-value {
flex: 1;
font-size: 24rpx;
color: rgba(0, 0, 0, 0.6);
text-align: right;
line-height: 1.55;
}
.info-value--wrap {
text-align: right;
}
.info-value--fee {
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
gap: 12rpx;
}
.info-value-txt {
font-size: 24rpx;
color: rgba(0, 0, 0, 0.6);
}
.fee-inline {
display: inline-flex;
align-items: baseline;
gap: 2rpx;
}
.fee-currency {
font-size: 24rpx;
font-weight: 600;
color: #05419b;
}
.fee-digits {
font-size: 24rpx;
color: rgba(0, 0, 0, 0.6);
}
.info-i {
width: 28rpx;
height: 28rpx;
line-height: 26rpx;
text-align: center;
font-size: 20rpx;
color: #1a57b2;
border: 2rpx solid rgba(26, 87, 178, 0.45);
border-radius: 50%;
}
.notice-card,
.limit-card {
margin-top: 32rpx;
padding: 24rpx 22rpx 28rpx;
background: #f7f8fd;
border-radius: 16rpx;
border: 1rpx solid rgba(0, 0, 0, 0.05);
box-sizing: border-box;
}
.project-panel {
margin-top: 0;
padding: 0;
background: transparent;
border: none;
}
.card-head {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.card-icon {
width: 44rpx;
height: 44rpx;
margin-right: 14rpx;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
line-height: 1;
}
.card-icon--speaker,
.card-icon--doc {
background: #1a57b2;
box-shadow: 0 4rpx 12rpx rgba(26, 87, 178, 0.22);
}
.card-head-title {
font-size: 30rpx;
font-weight: 700;
color: #333333;
}
.notice-body {
padding-left: 4rpx;
}
.notice-p,
.notice-row,
.notice-ranking {
margin-bottom: 18rpx;
}
.notice-p:last-child,
.notice-row:last-child,
.notice-ranking:last-child {
margin-bottom: 0;
}
.notice-strong {
font-size: 24rpx;
font-weight: 600;
color: #333333;
}
.notice-text {
font-size: 24rpx;
color: rgba(0, 0, 0, 0.6);
line-height: 1.65;
}
.ranking-lines {
display: flex;
flex-direction: column;
gap: 8rpx;
margin-top: 8rpx;
}
.ranking-line {
display: block;
}
.section-stack {
margin-top: 8rpx;
padding: 24rpx;
background: #ffffff;
border-radius: 16rpx;
padding: 14rpx 16rpx;
color: #333;
font-size: 26rpx;
border: 1rpx solid #ebedf0;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
box-sizing: border-box;
}
.label {
color: #666;
.section-stack .limit-card {
margin-top: 0;
}
.section {
margin-top: 18rpx;
background: #ffffff;
border-radius: 16rpx;
padding: 16rpx;
.section-stack .project-panel {
margin-top: 0;
}
.section-stack .limit-card + .project-panel {
margin-top: 24rpx;
}
.section-with-bullet {
margin-top: 40rpx;
}
.section-head {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.section-bullet {
width: 16rpx;
height: 16rpx;
margin-right: 14rpx;
border: 4rpx solid #1a57b2;
border-radius: 50%;
box-sizing: border-box;
flex-shrink: 0;
background: transparent;
}
.section-title {
font-size: 28rpx;
font-weight: 800;
margin-bottom: 12rpx;
font-weight: 600;
color: #333333;
}
.intro {
color: #555;
font-size: 26rpx;
line-height: 1.6;
.limit-list {
padding-left: 4rpx;
}
.project-card {
padding: 14rpx 0;
border-top: 1rpx solid rgba(0, 0, 0, 0.06);
.limit-item {
display: flex;
align-items: flex-start;
margin-bottom: 16rpx;
font-size: 24rpx;
line-height: 1.65;
color: rgba(0, 0, 0, 0.6);
}
.project-card:first-child {
border-top: none;
padding-top: 0;
.limit-item:last-child {
margin-bottom: 0;
}
.pname {
.limit-num {
flex-shrink: 0;
margin-right: 8rpx;
color: #333333;
font-weight: 600;
}
.limit-text {
flex: 1;
}
.table-wrap {
overflow: hidden;
border-radius: 16rpx;
border: 1rpx solid #ebedf0;
}
.table-head {
display: flex;
align-items: stretch;
background: #1a57b2;
color: #ffffff;
font-size: 22rpx;
font-weight: 600;
padding: 20rpx 12rpx;
}
.th {
text-align: center;
line-height: 1.35;
}
.th-type {
flex: 0.9;
}
.th-proj {
flex: 1.15;
}
.th-group {
flex: 1.15;
}
.th-fee {
flex: 0.85;
}
.table-body {
background: #ffffff;
}
.tr {
display: flex;
align-items: stretch;
padding: 20rpx 12rpx;
border-bottom: 1rpx solid #f0f0f0;
font-size: 24rpx;
color: #666666;
}
.tr:last-child {
border-bottom: none;
}
.td {
text-align: center;
line-height: 1.45;
word-break: break-all;
}
.td-type {
flex: 0.9;
color: #666666;
}
.td-proj {
flex: 1.15;
}
.td-group {
flex: 1.15;
}
.td-fee {
flex: 0.85;
font-weight: 400;
color: #666666;
}
.intro-block {
padding: 8rpx 0 8rpx;
}
.intro-text {
display: block;
font-size: 24rpx;
color: #333333;
line-height: 1.75;
}
.intro-image {
display: block;
width: 100%;
margin-top: 24rpx;
border-radius: 16rpx;
overflow: hidden;
background: #eee;
}
.participant-section {
margin-top: 40rpx;
padding-bottom: 8rpx;
}
.participant-head {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.participant-hex {
width: 22rpx;
height: 26rpx;
margin-right: 12rpx;
flex-shrink: 0;
background: #1a57b2;
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
}
.participant-title {
font-size: 28rpx;
font-weight: 900;
color: #222;
margin-bottom: 10rpx;
font-weight: 600;
color: #333333;
}
.pinfo {
margin-top: 8rpx;
color: #666;
font-size: 24rpx;
.participant-card {
padding: 8rpx 24rpx 12rpx;
background: #f7f8fd;
border-radius: 16rpx;
border: 1rpx solid rgba(0, 0, 0, 0.05);
box-sizing: border-box;
}
.groups {
margin-top: 12rpx;
color: #444;
.participant-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24rpx;
padding: 22rpx 0;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
}
.participant-row:last-child {
border-bottom: none;
}
.participant-label {
flex-shrink: 0;
font-size: 24rpx;
color: rgba(0, 0, 0, 0.6);
line-height: 1.55;
}
.participant-value {
flex: 1;
font-size: 24rpx;
color: #333333;
text-align: right;
line-height: 1.55;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 18rpx 16rpx 24rpx;
background: rgba(255, 255, 255, 0.95);
bottom: 0;
z-index: 100;
padding: 18rpx 40rpx;
padding-bottom: calc(18rpx + env(safe-area-inset-bottom));
background: rgba(255, 255, 255, 0.96);
border-top: 1rpx solid rgba(0, 0, 0, 0.06);
box-shadow: 0 -4rpx 24rpx rgba(0, 0, 0, 0.04);
}
.apply-btn {
width: 100%;
height: 86rpx;
height: 120rpx;
line-height: 120rpx;
font-size: 32rpx;
font-weight: 700;
border: none;
border-radius: 32rpx;
color: #ffffff !important;
background: #1a57b2 !important;
box-shadow: 0 16rpx 60rpx rgba(0, 0, 0, 0.06);
}
.apply-btn::after {
border: none;
}

View File

@ -2,6 +2,44 @@ const { API } = require('../../utils/constants');
const { request } = require('../../utils/http');
const { formatDateTime, eventStatusText, eventFormatText } = require('../../utils/format');
/** 统一后台轮播字段(camelCase / PascalCase、枚举数字)供 wxml 绑定 */
function normalizeBannerItem(raw) {
if (!raw || typeof raw !== 'object') {
return { imageUrl: '', linkType: '', eventId: null, linkUrl: '' };
}
const imageUrl = raw.imageUrl || raw.ImageUrl || '';
const linkUrl = String(raw.linkUrl || raw.LinkUrl || '').trim();
let linkType = raw.linkType != null ? raw.linkType : raw.LinkType;
const rawEventId = raw.eventId != null ? raw.eventId : raw.EventId;
const eventId =
rawEventId != null && rawEventId !== '' ? Number(rawEventId) : null;
const idOk = Number.isFinite(eventId) && eventId > 0;
if (typeof linkType === 'number') {
if (linkType === 1) linkType = 'Event';
else if (linkType === 2) linkType = 'Url';
else if (linkType === 0) linkType = '';
else linkType = String(linkType);
} else if (typeof linkType === 'string') {
const lower = linkType.toLowerCase();
if (lower === 'event') linkType = 'Event';
else if (lower === 'url' || lower === 'link' || lower === 'external') linkType = 'Url';
}
if (linkType == null || linkType === '' || linkType === '0') {
if (idOk) linkType = 'Event';
else if (linkUrl) linkType = 'Url';
else linkType = '';
}
return {
imageUrl,
linkType,
eventId: idOk ? eventId : null,
linkUrl
};
}
Page({
data: {
sectionTab: 'list',
@ -66,9 +104,12 @@ Page({
needAuth: false
})
.then((data) => {
this.setData({
banners: Array.isArray(data) ? data : []
});
const arr = Array.isArray(data) ? data : [];
const banners = arr.map((item, index) => ({
...normalizeBannerItem(item),
_bannerKey: index
}));
this.setData({ banners });
})
.catch(() => {
this.setData({ banners: [] });
@ -140,10 +181,10 @@ Page({
onBannerTap(e) {
const linkType = e.currentTarget.dataset.linktype;
const eventId = e.currentTarget.dataset.eventid;
const linkUrl = e.currentTarget.dataset.linkurl;
const eventId = Number(e.currentTarget.dataset.eventid);
const linkUrl = e.currentTarget.dataset.linkurl || '';
if (linkType === 'Event' && eventId) {
if (linkType === 'Event' && eventId > 0) {
wx.navigateTo({ url: `/pages/detail/detail?id=${eventId}` });
return;
}

View File

@ -21,7 +21,7 @@
indicator-color="rgba(255,255,255,0.55)"
indicator-active-color="#ffffff"
>
<block wx:for="{{banners}}" wx:key="imageUrl">
<block wx:for="{{banners}}" wx:key="_bannerKey">
<swiper-item>
<view
class="banner-item"

View File

@ -5,13 +5,13 @@
"appid": "wxf5a3f98d44c3a563",
"setting": {
"urlCheck": true,
"es6": false,
"es6": true,
"postcss": false,
"compileWorklet": false,
"minified": false,
"uglifyFileName": false,
"uploadWithSourceMap": true,
"enhance": false,
"enhance": true,
"packNpmManually": false,
"packNpmRelationList": [],
"minifyWXSS": true,