diff --git a/app.json b/app.json
index 870840f..80fe614 100644
--- a/app.json
+++ b/app.json
@@ -45,7 +45,8 @@
"team/team",
"team-detail/team-detail",
"order/order",
- "order-detail/order-detail"
+ "order-detail/order-detail",
+ "cancel-registration/cancel-registration"
]
}
],
diff --git a/pages/detail/detail.js b/pages/detail/detail.js
index b35d4d4..a99652b 100644
--- a/pages/detail/detail.js
+++ b/pages/detail/detail.js
@@ -15,7 +15,7 @@ var DETAIL_UI_FALLBACK = {
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积分',
diff --git a/pages/home/home.js b/pages/home/home.js
index 20ae2dc..b141c9b 100644
--- a/pages/home/home.js
+++ b/pages/home/home.js
@@ -40,6 +40,15 @@ function normalizeBannerItem(raw) {
};
}
+function canCancelByEventStatus(rawEventStatus) {
+ // 仅报名中(Enrolling / 1)允许取消
+ if (rawEventStatus === 'Enrolling') return true;
+ const n = Number(rawEventStatus);
+ if (!Number.isNaN(n)) return n === 1;
+ const s = rawEventStatus == null ? '' : String(rawEventStatus).toLowerCase();
+ return s === 'enrolling' || s === '1';
+}
+
Page({
data: {
sectionTab: 'list',
@@ -201,6 +210,62 @@ Page({
wx.navigateTo({ url: `/pages/detail/detail?id=${id}` });
},
+ onCancelRegistrationTap(e) {
+ const registrationId = Number(e.currentTarget.dataset.registrationid);
+ if (!registrationId) return;
+ wx.navigateTo({
+ url: `/subpackages/mine/cancel-registration/cancel-registration?registrationId=${registrationId}`
+ });
+ },
+
+ onUploadMaterialTap(e) {
+ const registrationId = Number(e.currentTarget.dataset.registrationid);
+ if (!registrationId) return;
+ this.openUploadPicker({ kind: 'material', registrationId });
+ },
+
+ onUploadVoucherTap(e) {
+ const registrationId = Number(e.currentTarget.dataset.registrationid);
+ if (!registrationId) return;
+ this.openUploadPicker({ kind: 'voucher', registrationId });
+ },
+
+ openUploadPicker({ kind, registrationId }) {
+ const title = kind === 'voucher' ? '上传付款凭证' : '上传素材';
+ wx.showActionSheet({
+ itemList: ['从相册选择', '拍照'],
+ success: (r) => {
+ if (!r || typeof r.tapIndex !== 'number') return;
+ const sourceType = r.tapIndex === 1 ? ['camera'] : ['album'];
+ wx.chooseMedia({
+ count: 1,
+ mediaType: ['image'],
+ sourceType,
+ success: (res) => {
+ const tempFilePath =
+ res && res.tempFiles && res.tempFiles[0] ? res.tempFiles[0].tempFilePath : '';
+ if (!tempFilePath) return;
+
+ wx.showModal({
+ title,
+ content: '确认上传该图片?',
+ cancelText: '取消',
+ confirmText: '确定',
+ success: (mr) => {
+ if (!mr || !mr.confirm) return;
+ // TODO: 接入后端上传接口(素材/付款凭证)
+ // kind: material | voucher
+ // registrationId: 报名记录 id
+ // tempFilePath: 本地图片路径
+ wx.showToast({ title: '已选择', icon: 'success' });
+ }
+ });
+ }
+ });
+ }
+ });
+ },
+
loadEvents(reset) {
if (this.data.loading) return Promise.resolve();
@@ -318,17 +383,29 @@ Page({
const rows = (res && Array.isArray(res.rows) ? res.rows : []).map((item) => {
const eid = item.eventId;
const rid = item.registrationId;
+ const rawEventStatus =
+ item.eventStatus != null
+ ? item.eventStatus
+ : item.EventStatus != null
+ ? item.EventStatus
+ : item.status != null
+ ? item.status
+ : item.Status;
+ const eventStatusPill = eventStatusForPill(rawEventStatus);
return {
id: eid,
rowKey: `m-${rid}`,
+ registrationId: rid,
eventName: item.eventName,
coverImage: item.coverImage,
address: '',
- eventStatusPill: 1,
- eventStatusText: '已报名',
+ eventStatusPill,
+ eventStatusText: eventStatusText(rawEventStatus) || '已报名',
registrationTimeText: formatDateTime(item.registrationTime),
projectCount: item.projectCount,
- eventFormatText: ''
+ eventFormatText: '',
+ paymentStatus: item.paymentStatus != null ? Number(item.paymentStatus) : null,
+ canCancel: canCancelByEventStatus(rawEventStatus)
};
});
diff --git a/pages/home/home.wxml b/pages/home/home.wxml
index c9f76a4..6819374 100644
--- a/pages/home/home.wxml
+++ b/pages/home/home.wxml
@@ -129,6 +129,33 @@
已报项目:{{item.projectCount}} 项
+
+
+ 上传素材
+
+
+ 已支付
+
+
+ 上传付款凭证
+
+
+ 更改参赛人
+ 取消报名
+
diff --git a/pages/home/home.wxss b/pages/home/home.wxss
index 4f4f372..7b31565 100644
--- a/pages/home/home.wxss
+++ b/pages/home/home.wxss
@@ -295,6 +295,44 @@ page {
padding: 22rpx 20rpx 26rpx;
}
+.card-actions {
+ display: flex;
+ gap: 16rpx;
+ padding: 0 20rpx 22rpx;
+ flex-wrap: nowrap;
+}
+
+.action-btn {
+ flex: 1;
+ height: 64rpx;
+ line-height: 64rpx;
+ text-align: center;
+ font-size: 24rpx;
+ border-radius: 10rpx;
+ border: 2rpx solid #e1e3e6;
+ background: #fff;
+ color: #323233;
+ box-sizing: border-box;
+}
+
+.action-btn--ghost {
+ background: #fff;
+ color: #323233;
+}
+
+.action-btn--danger {
+ border-color: #e6060b;
+ color: #e6060b;
+ background: #fff;
+}
+
+.action-btn--paid {
+ border-color: #00b56d;
+ background: rgba(0, 181, 109, 0.12);
+ color: #00b56d;
+ font-weight: 700;
+}
+
.title-row {
display: flex;
flex-wrap: wrap;
diff --git a/pages/mine/mine.wxml b/pages/mine/mine.wxml
index 8685399..41ce1b2 100644
--- a/pages/mine/mine.wxml
+++ b/pages/mine/mine.wxml
@@ -73,8 +73,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/subpackages/mine/cancel-registration/cancel-registration.js b/subpackages/mine/cancel-registration/cancel-registration.js
new file mode 100644
index 0000000..699f2b8
--- /dev/null
+++ b/subpackages/mine/cancel-registration/cancel-registration.js
@@ -0,0 +1,155 @@
+const { API } = require('../../../utils/constants');
+const { request } = require('../../../utils/http');
+
+function toNumber(v, fallback = 0) {
+ const n = Number(v);
+ return Number.isFinite(n) ? n : fallback;
+}
+
+function formatMoney(v) {
+ const n = toNumber(v, 0);
+ return (Math.round(n * 100) / 100).toFixed(2).replace(/\.00$/, '');
+}
+
+function genderText(v) {
+ if (v == null) return '';
+ const s = String(v).toLowerCase();
+ if (s === 'male' || s === 'm' || s === '1') return '男';
+ if (s === 'female' || s === 'f' || s === '2') return '女';
+ return '';
+}
+
+Page({
+ data: {
+ registrationId: null,
+ loading: true,
+
+ refundRulesText: '退款规则以赛事说明为准',
+
+ // items: [{ itemKey, projectName, pills: [{ pillKey, label, price, selected }] }]
+ items: [],
+ selectAll: false,
+ selectedCount: 0,
+ refundAmount: '0'
+ },
+
+ onLoad(options) {
+ const registrationId = Number(options.registrationId);
+ this.setData({ registrationId });
+ if (!registrationId) {
+ this.setData({ loading: false });
+ wx.showToast({ title: '参数错误', icon: 'none' });
+ return;
+ }
+ this.fetchRegistrationDetail(registrationId);
+ },
+
+ fetchRegistrationDetail(registrationId) {
+ this.setData({ loading: true });
+ request({
+ path: API.myRegistrationDetail(registrationId),
+ method: 'GET',
+ needAuth: true
+ })
+ .then((detail) => {
+ const d = detail && typeof detail === 'object' ? detail : {};
+ const rawItems = Array.isArray(d.items) ? d.items : [];
+
+ const items = rawItems.map((it, idx) => {
+ const projectName = it.projectName || it.ProjectName || `项目${idx + 1}`;
+ const groupName = it.groupName || it.GroupName || '';
+ const price = it.price != null ? toNumber(it.price, 0) : 0;
+ const persons = Array.isArray(it.persons) ? it.persons : [];
+
+ const pills =
+ persons.length > 0
+ ? persons.map((p, pi) => {
+ const gender = genderText(p.gender);
+ const label = [groupName, gender].filter(Boolean).join(' | ') || groupName || '参赛人';
+ return {
+ pillKey: `i-${idx}-p-${pi}`,
+ label,
+ price,
+ selected: true
+ };
+ })
+ : [
+ {
+ pillKey: `i-${idx}-g`,
+ label: groupName || '组别',
+ price,
+ selected: true
+ }
+ ];
+
+ return {
+ itemKey: `it-${idx}`,
+ projectName,
+ pills
+ };
+ });
+
+ // 默认全选
+ this.setData({
+ items,
+ selectAll: items.length > 0,
+ loading: false
+ });
+ this.recalc();
+ })
+ .catch((err) => {
+ this.setData({ loading: false, items: [] });
+ wx.showToast({ title: (err && err.message) || '加载失败', icon: 'none' });
+ });
+ },
+
+ onTogglePill(e) {
+ const pillKey = e.currentTarget.dataset.pillkey;
+ if (!pillKey) return;
+ const items = this.data.items.map((it) => ({
+ ...it,
+ pills: it.pills.map((p) => (p.pillKey === pillKey ? { ...p, selected: !p.selected } : p))
+ }));
+ this.setData({ items });
+ this.recalc();
+ },
+
+ onToggleAll() {
+ const next = !this.data.selectAll;
+ const items = this.data.items.map((it) => ({
+ ...it,
+ pills: it.pills.map((p) => ({ ...p, selected: next }))
+ }));
+ this.setData({ items, selectAll: next });
+ this.recalc();
+ },
+
+ recalc() {
+ const all = [];
+ this.data.items.forEach((it) => it.pills.forEach((p) => all.push(p)));
+ const selected = all.filter((p) => p.selected);
+ const sum = selected.reduce((acc, p) => acc + toNumber(p.price, 0), 0);
+ const selectAll = all.length > 0 && selected.length === all.length;
+ this.setData({
+ selectedCount: selected.length,
+ selectAll,
+ refundAmount: formatMoney(sum)
+ });
+ },
+
+ onConfirmCancel() {
+ if (this.data.selectedCount === 0) return;
+ wx.showModal({
+ title: '确认取消报名?',
+ content: `本次预计退款金额 ¥${this.data.refundAmount}`,
+ cancelText: '取消',
+ confirmText: '确定',
+ success: (res) => {
+ if (!res || !res.confirm) return;
+ // TODO: 接入后端“取消报名/退款”接口后提交所选项目
+ wx.showToast({ title: '已提交(待接接口)', icon: 'none' });
+ }
+ });
+ }
+});
+
diff --git a/subpackages/mine/cancel-registration/cancel-registration.json b/subpackages/mine/cancel-registration/cancel-registration.json
new file mode 100644
index 0000000..f097bb7
--- /dev/null
+++ b/subpackages/mine/cancel-registration/cancel-registration.json
@@ -0,0 +1,6 @@
+{
+ "navigationBarTitleText": "取消报名",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black"
+}
+
diff --git a/subpackages/mine/cancel-registration/cancel-registration.wxml b/subpackages/mine/cancel-registration/cancel-registration.wxml
new file mode 100644
index 0000000..251596d
--- /dev/null
+++ b/subpackages/mine/cancel-registration/cancel-registration.wxml
@@ -0,0 +1,53 @@
+
+
+
+ 注意
+
+
+ 退款规则:
+ {{refundRulesText}}
+
+
+ 退款路径:
+ 原路退款,请注意查收
+
+
+
+
+
+
+
+ {{regItem.projectName}}
+
+
+
+ {{pill.label}}
+
+
+
+
+
+
+
+ {{loading ? '加载中...' : '暂无可取消项目'}}
+
+
+
+
+
diff --git a/subpackages/mine/cancel-registration/cancel-registration.wxss b/subpackages/mine/cancel-registration/cancel-registration.wxss
new file mode 100644
index 0000000..5b947a2
--- /dev/null
+++ b/subpackages/mine/cancel-registration/cancel-registration.wxss
@@ -0,0 +1,161 @@
+.page {
+ min-height: 100%;
+ background: #f5f6f8;
+ padding: 20rpx 20rpx 140rpx;
+ box-sizing: border-box;
+}
+
+.notice {
+ background: #fff;
+ border-radius: 16rpx;
+ padding: 22rpx 22rpx 18rpx;
+ box-sizing: border-box;
+ border: 2rpx solid rgba(26, 87, 178, 0.28);
+}
+
+.notice-head {
+ display: flex;
+ align-items: center;
+ margin-bottom: 12rpx;
+}
+
+.notice-title {
+ font-size: 28rpx;
+ font-weight: 800;
+ color: #1a57b2;
+}
+
+.notice-row {
+ display: flex;
+ font-size: 24rpx;
+ color: #646566;
+ line-height: 1.6;
+ margin-top: 6rpx;
+}
+
+.notice-label {
+ flex-shrink: 0;
+ color: #323233;
+}
+
+.notice-text {
+ flex: 1;
+}
+
+.items {
+ margin-top: 18rpx;
+}
+
+.item-card {
+ background: #fff;
+ border-radius: 16rpx;
+ padding: 18rpx 18rpx 12rpx;
+ margin-top: 18rpx;
+}
+
+.item-title-text {
+ font-size: 26rpx;
+ font-weight: 800;
+ color: #111;
+}
+
+.pill-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 14rpx;
+ margin-top: 14rpx;
+}
+
+.pill {
+ padding: 10rpx 16rpx;
+ border-radius: 999rpx;
+ border: 2rpx solid #e1e3e6;
+ font-size: 22rpx;
+ color: #323233;
+ background: #fff;
+}
+
+.pill.is-on {
+ background: #1a57b2;
+ border-color: #1a57b2;
+ color: #fff;
+ font-weight: 700;
+}
+
+.empty {
+ padding: 140rpx 0;
+ text-align: center;
+}
+
+.empty-text {
+ font-size: 26rpx;
+ color: #969799;
+}
+
+.footer {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 18rpx 20rpx calc(env(safe-area-inset-bottom) + 18rpx);
+ background: #fff;
+ display: flex;
+ align-items: center;
+ gap: 18rpx;
+ box-shadow: 0 -10rpx 30rpx rgba(0, 0, 0, 0.06);
+}
+
+.footer-left {
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+ padding: 8rpx 10rpx;
+}
+
+.check {
+ width: 34rpx;
+ height: 34rpx;
+ border-radius: 999rpx;
+ border: 2rpx solid #c8c9cc;
+ box-sizing: border-box;
+ background: #fff;
+}
+
+.check.is-on {
+ border-color: #1a57b2;
+ background: #1a57b2;
+}
+
+.footer-left-text {
+ font-size: 24rpx;
+ color: #646566;
+}
+
+.footer-btn {
+ flex: 1;
+ height: 90rpx;
+ border-radius: 14rpx;
+ background: #1a57b2;
+ color: #fff;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.footer-btn.is-disabled {
+ opacity: 0.45;
+}
+
+.footer-btn-sub {
+ font-size: 22rpx;
+ line-height: 1.2;
+}
+
+.footer-btn-main {
+ font-size: 30rpx;
+ font-weight: 800;
+ line-height: 1.2;
+ margin-top: 4rpx;
+}
+
diff --git a/utils/constants.js b/utils/constants.js
index ab70cd1..f1909cc 100644
--- a/utils/constants.js
+++ b/utils/constants.js
@@ -8,6 +8,8 @@ const API = {
eventList: '/api/eventclient/list',
eventMyList: '/api/eventclient/my-events',
eventDetail: (id) => `/api/eventclient/${id}`,
+ eventRegistrationStatus: (registrationId) => `/api/eventclient/status/${registrationId}`,
+ myRegistrationDetail: (registrationId) => `/api/eventclient/my-registration/${registrationId}`,
apply: '/api/eventclient/apply',
calculateAmount: '/api/eventclient/calculate-amount',