From 7404498d4648f7e1efdd09bdcd62491f7a8bf9b2 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Wed, 13 May 2026 00:36:39 +0800 Subject: [PATCH] update --- miniprogram/app.js | 7 +- miniprogram/app.json | 3 +- miniprogram/app.wxss | 251 ++++++++++++++++++- miniprogram/pages/bind/index.js | 10 +- miniprogram/pages/bind/index.json | 3 + miniprogram/pages/home/index.js | 44 +++- miniprogram/pages/home/index.wxml | 7 +- miniprogram/pages/manage/index.js | 122 ++++++++- miniprogram/pages/manage/index.json | 3 + miniprogram/pages/manage/index.wxml | 146 +++++++---- miniprogram/pages/manage/index.wxss | 36 ++- miniprogram/pages/mine/index.js | 5 +- miniprogram/pages/module/index.js | 84 ++++++- miniprogram/pages/module/index.wxml | 58 ++++- miniprogram/pages/profile-edit/index.js | 19 ++ miniprogram/pages/profile-edit/index.wxml | 48 +++- miniprogram/pages/profile-edit/index.wxss | 13 +- miniprogram/pages/schedule-detail/index.js | 23 +- miniprogram/pages/schedule-detail/index.wxml | 8 +- miniprogram/pages/timeline-create/index.js | 2 +- miniprogram/pages/timeline-create/index.wxml | 34 +-- miniprogram/pages/timeline-create/index.wxss | 13 +- miniprogram/pages/timeline-detail/index.js | 7 +- miniprogram/pages/vote-detail/index.js | 107 ++++++++ miniprogram/pages/vote-detail/index.json | 4 + miniprogram/pages/vote-detail/index.wxml | 56 +++++ miniprogram/pages/vote-detail/index.wxss | 57 +++++ miniprogram/utils/api.js | 3 +- miniprogram/utils/auth.js | 4 +- miniprogram/utils/config.js | 2 +- miniprogram/utils/modules.js | 4 +- miniprogram/utils/page-helpers.js | 20 +- miniprogram/utils/permissions.js | 9 +- 33 files changed, 1059 insertions(+), 153 deletions(-) create mode 100644 miniprogram/pages/bind/index.json create mode 100644 miniprogram/pages/manage/index.json create mode 100644 miniprogram/pages/vote-detail/index.js create mode 100644 miniprogram/pages/vote-detail/index.json create mode 100644 miniprogram/pages/vote-detail/index.wxml create mode 100644 miniprogram/pages/vote-detail/index.wxss diff --git a/miniprogram/app.js b/miniprogram/app.js index 4d440d1..a339255 100644 --- a/miniprogram/app.js +++ b/miniprogram/app.js @@ -17,13 +17,16 @@ App({ setUser(user) { const savedClass = wx.getStorageSync("active_class") || null; - const savedClassValid = savedClass?.id && user.memberships?.some( + const memberships = Array.isArray(user.memberships) ? user.memberships : []; + const savedClassValid = savedClass && savedClass.id && memberships.some( (membership) => membership.class_id === savedClass.id ); + const activeMembership = user.active_membership || null; + const firstMembership = memberships.length ? memberships[0] : null; this.globalData.user = user; this.globalData.activeClassId = savedClassValid ? savedClass.id - : user.active_membership?.class_id || user.memberships?.[0]?.class_id || null; + : (activeMembership && activeMembership.class_id) || (firstMembership && firstMembership.class_id) || null; this.globalData.enabledModules = savedClassValid ? savedClass.enabled_modules : user.enabled_modules || null; diff --git a/miniprogram/app.json b/miniprogram/app.json index 72212e8..8a71219 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -1,14 +1,15 @@ { "pages": [ + "pages/bind/index", "pages/home/index", "pages/class/index", "pages/interact/index", "pages/mine/index", - "pages/bind/index", "pages/module/index", "pages/manage/index", "pages/member-detail/index", "pages/schedule-detail/index", + "pages/vote-detail/index", "pages/timeline-detail/index", "pages/timeline-create/index", "pages/profile-edit/index", diff --git a/miniprogram/app.wxss b/miniprogram/app.wxss index 61b0853..fcbeb33 100644 --- a/miniprogram/app.wxss +++ b/miniprogram/app.wxss @@ -230,6 +230,151 @@ page { box-shadow: none; } +.fab-button { + position: fixed; + right: 34rpx; + bottom: calc(44rpx + env(safe-area-inset-bottom)); + z-index: 40; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + width: 108rpx; + min-width: 108rpx; + max-width: 108rpx; + height: 108rpx; + min-height: 108rpx; + margin: 0; + padding: 0; + border: 0; + border-radius: 999rpx; + background: #6b1f2b; + box-shadow: 0 18rpx 42rpx rgba(107, 31, 43, 0.28); + color: #fff8ed; + font-size: 62rpx; + font-weight: 420; + line-height: 108rpx; +} + +.fab-button::after { + border: 0; +} + +.fab-spacer { + height: 144rpx; +} + +.form-page { + padding-bottom: calc(140rpx + env(safe-area-inset-bottom)); +} + +.form-header { + margin-bottom: 26rpx; +} + +.form-kicker { + color: #8b5a36; + font-size: 22rpx; + font-weight: 760; + letter-spacing: 0; +} + +.form-title { + margin-top: 8rpx; + color: #31211c; + font-size: 44rpx; + font-weight: 780; + line-height: 1.18; +} + +.form-subtitle { + margin-top: 10rpx; + color: #8a7b70; + font-size: 25rpx; + line-height: 1.5; +} + +.form-card { + margin-bottom: 18rpx; + border: 1rpx solid rgba(121, 84, 54, 0.11); + border-radius: 28rpx; + background: rgba(255, 252, 247, 0.96); + padding: 28rpx; + box-shadow: 0 14rpx 36rpx rgba(68, 39, 27, 0.06); +} + +.form-field { + margin-top: 26rpx; +} + +.form-field:first-child { + margin-top: 0; +} + +.form-label { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12rpx; + color: #3a221d; + font-size: 25rpx; + font-weight: 730; +} + +.form-hint { + color: #9b8b7f; + font-size: 22rpx; + font-weight: 500; +} + +.form-input, +.form-textarea, +.form-select { + box-sizing: border-box; + width: 100%; + border: 1rpx solid rgba(121, 84, 54, 0.14); + border-radius: 22rpx; + background: #fffaf3; + color: #30211c; + font-size: 29rpx; +} + +.form-input, +.form-select { + min-height: 82rpx; + padding: 0 24rpx; + line-height: 82rpx; +} + +.form-textarea { + min-height: 190rpx; + padding: 22rpx 24rpx; + line-height: 1.5; +} + +.form-switch-row { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 82rpx; +} + +.form-submit-bar { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 30; + box-sizing: border-box; + padding: 18rpx 32rpx calc(18rpx + env(safe-area-inset-bottom)); + background: rgba(250, 244, 235, 0.92); + backdrop-filter: blur(18rpx); +} + +.form-submit-bar .button { + margin-top: 0; +} + .empty { margin-top: 120rpx; text-align: center; @@ -272,14 +417,30 @@ page { font-weight: 650; } +.pill.done { + background: #dff3e8; + color: #1f7a4d; +} + .member-row, .schedule-row, -.feed-head { +.feed-head, +.vote-row { display: flex; align-items: center; gap: 22rpx; } +.vote-meta { + display: flex; + flex-wrap: wrap; + gap: 10rpx 18rpx; + margin-top: 14rpx; + color: #8a7b70; + font-size: 23rpx; + line-height: 1.35; +} + .more-dot { flex: none; min-width: 58rpx; @@ -360,6 +521,94 @@ page { font-size: 24rpx; } +.fund-summary { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 14rpx; +} + +.fund-summary-item { + min-height: 126rpx; + box-sizing: border-box; + padding: 20rpx 16rpx; + border: 1rpx solid rgba(121, 84, 54, 0.1); + border-radius: 26rpx; + background: rgba(255, 252, 247, 0.96); + box-shadow: 0 14rpx 34rpx rgba(68, 39, 27, 0.06); +} + +.fund-summary-item.strong { + background: #6b1f2b; +} + +.fund-summary-item.strong .fund-label, +.fund-summary-item.strong .fund-number { + color: #fff8ed; +} + +.fund-label { + color: #8a7b70; + font-size: 22rpx; + font-weight: 650; +} + +.fund-number { + margin-top: 14rpx; + color: #30211c; + font-size: 28rpx; + font-weight: 780; +} + +.fund-number.income, +.fund-amount.income { + color: #1f7a4d; +} + +.fund-number.expense, +.fund-amount.expense { + color: #9a3a2f; +} + +.fund-row { + display: flex; + align-items: flex-start; + gap: 20rpx; +} + +.fund-type-badge { + flex: none; + min-width: 72rpx; + height: 48rpx; + border-radius: 999rpx; + font-size: 23rpx; + font-weight: 750; + line-height: 48rpx; + text-align: center; +} + +.fund-type-badge.income { + background: #dff3e8; + color: #1f7a4d; +} + +.fund-type-badge.expense { + background: #f7e1dc; + color: #9a3a2f; +} + +.fund-row-top { + display: flex; + align-items: center; + justify-content: space-between; + gap: 18rpx; +} + +.fund-amount { + flex: none; + font-size: 30rpx; + font-weight: 780; +} + input, textarea, picker { diff --git a/miniprogram/pages/bind/index.js b/miniprogram/pages/bind/index.js index bdb7c6d..92ba772 100644 --- a/miniprogram/pages/bind/index.js +++ b/miniprogram/pages/bind/index.js @@ -20,6 +20,12 @@ Page({ password: "" }, + onLoad() { + if (wx.getStorageSync("auth_token")) { + wx.switchTab({ url: "/pages/home/index" }); + } + }, + switchLoginMethod(event) { const method = event.currentTarget.dataset.method; this.setData({ @@ -102,7 +108,7 @@ Page({ }, async bindWithPhone(event) { - const phoneCode = event.detail?.code; + const phoneCode = event.detail && event.detail.code; if (!phoneCode) { wx.showToast({ title: "需要授权手机号完成绑定", icon: "none" }); return; @@ -129,7 +135,7 @@ Page({ }, bindCurrentWithPhone(event) { - const phoneCode = event.detail?.code; + const phoneCode = event.detail && event.detail.code; if (!phoneCode) { wx.showToast({ title: "需要授权手机号完成绑定", icon: "none" }); return; diff --git a/miniprogram/pages/bind/index.json b/miniprogram/pages/bind/index.json new file mode 100644 index 0000000..c5a131f --- /dev/null +++ b/miniprogram/pages/bind/index.json @@ -0,0 +1,3 @@ +{ + "navigationBarTitleText": "登录" +} diff --git a/miniprogram/pages/home/index.js b/miniprogram/pages/home/index.js index 83b7461..8654c58 100644 --- a/miniprogram/pages/home/index.js +++ b/miniprogram/pages/home/index.js @@ -3,6 +3,30 @@ const { refreshMe, requireLogin } = require("../../utils/auth"); const { isModuleEnabled, visibleModules } = require("../../utils/modules"); const { getActiveClassId, getActiveClassName, getEnabledModules, showError } = require("../../utils/page-helpers"); +function formatDateTime(value) { + if (!value) return ""; + return String(value).replace("T", " ").slice(0, 16); +} + +function formatScheduleTime(item) { + const start = formatDateTime(item.start_time); + const end = formatDateTime(item.end_time); + if (item.type === "deadline") return `截止 ${start}`; + if (!end) return start; + if (start.slice(0, 10) === end.slice(0, 10)) { + return `${start} - ${end.slice(11)}`; + } + return `${start} - ${end}`; +} + +function scheduleTypeText(type) { + return { + course: "课程", + deadline: "截止日", + activity: "活动" + }[type] || type || "排期"; +} + Page({ data: { className: "HKU ICB", @@ -62,8 +86,20 @@ Page({ const value = results[index]; if (name === "unread") next.unreadCount = value.unread_count || 0; if (name === "announcements") next.announcements = value.items || []; - if (name === "schedules") next.schedules = value || []; - if (name === "votes") next.votes = value.items || []; + if (name === "schedules") { + next.schedules = (value || []).map((item) => ({ + ...item, + schedule_time_text: formatScheduleTime(item), + schedule_type_text: scheduleTypeText(item.type) + })); + } + if (name === "votes") { + next.votes = (value.items || []).map((item) => ({ + ...item, + vote_type_text: item.vote_type === "multiple" ? "多选" : "单选", + vote_action_text: item.has_voted ? "已参与" : "待参与" + })); + } if (name === "timelines") next.timelines = value.items || []; }); this.setData(next); @@ -83,6 +119,10 @@ Page({ wx.navigateTo({ url: `/pages/schedule-detail/index?id=${event.currentTarget.dataset.id}` }); }, + openVote(event) { + wx.navigateTo({ url: `/pages/vote-detail/index?id=${event.currentTarget.dataset.id}` }); + }, + openTimeline(event) { wx.navigateTo({ url: `/pages/timeline-detail/index?id=${event.currentTarget.dataset.id}` }); } diff --git a/miniprogram/pages/home/index.wxml b/miniprogram/pages/home/index.wxml index cceaeb7..fa38be6 100644 --- a/miniprogram/pages/home/index.wxml +++ b/miniprogram/pages/home/index.wxml @@ -60,8 +60,9 @@ {{item.title}} {{item.location || "地点待定"}} + {{item.schedule_time_text}} - {{item.type}} + {{item.schedule_type_text}} @@ -71,12 +72,12 @@ 班级投票 参与 - + {{item.title}} - {{item.has_voted ? "已参与" : "待参与"}} + {{item.vote_type_text}} · {{item.vote_action_text}} · {{item.total_voters}} 人参与 diff --git a/miniprogram/pages/manage/index.js b/miniprogram/pages/manage/index.js index 4783e7e..beed6a7 100644 --- a/miniprogram/pages/manage/index.js +++ b/miniprogram/pages/manage/index.js @@ -6,7 +6,7 @@ const { ensureModuleOpen, getActiveClassId, showError } = require("../../utils/p const FORM_DEFAULTS = { announcements: { title: "", content: "", is_pinned: false }, votes: { title: "", description: "", options_text: "", vote_type: "single", is_anonymous: false }, - schedule: { title: "", type: "course", start_time: "", location: "", description: "" }, + schedule: { title: "", type: "course", start_time: "", end_time: "", location: "", description: "" }, fund: { type: "expense", amount: "", category: "", description: "", record_date: "" } }; @@ -19,8 +19,20 @@ Page({ isVotes: false, isSchedule: false, isFund: false, - scheduleTypes: ["course", "deadline", "activity"], + isScheduleDeadline: false, + scheduleTimeLabel: "开始时间", + isFundIncome: false, + isFundExpense: true, + fundIncomeClass: "", + fundExpenseClass: "active expense", + scheduleTypes: ["课程", "截止日", "活动"], + scheduleTypeValues: ["course", "deadline", "activity"], + scheduleTypeLabel: "课程", fundTypes: ["income", "expense"], + fundTypeLabels: ["入账", "出账"], + fundIncomeCategories: ["班费收取", "活动赞助", "其他收入"], + fundExpenseCategories: ["聚餐", "活动物资", "场地费", "交通费", "礼品", "其他支出"], + fundCategories: ["聚餐", "活动物资", "场地费", "交通费", "礼品", "其他支出"], loading: false }, @@ -30,19 +42,37 @@ Page({ const classId = getActiveClassId(); const user = getApp().globalData.user; if (!module || !ensureModuleOpen(moduleKey) || !hasManagePermission(user, classId, moduleKey)) { + const unavailableTitle = module ? module.title : "功能"; wx.redirectTo({ - url: `/pages/module-unavailable/index?title=${encodeURIComponent(module?.title || "功能")}` + url: `/pages/module-unavailable/index?title=${encodeURIComponent(unavailableTitle)}` }); return; } + const form = Object.assign({}, FORM_DEFAULTS[moduleKey] || {}); + if (moduleKey === "fund" && !form.record_date) { + form.record_date = new Date().toISOString().slice(0, 10); + } + if (moduleKey === "fund" && !form.category) { + form.category = form.type === "income" ? "班费收取" : "聚餐"; + } this.setData({ moduleKey, title: `新增${module.title}`, - form: Object.assign({}, FORM_DEFAULTS[moduleKey] || {}), + form, isAnnouncements: moduleKey === "announcements", isVotes: moduleKey === "votes", isSchedule: moduleKey === "schedule", - isFund: moduleKey === "fund" + isScheduleDeadline: moduleKey === "schedule" && form.type === "deadline", + scheduleTimeLabel: form.type === "deadline" ? "截止时间" : "开始时间", + scheduleTypeLabel: moduleKey === "schedule" ? this.scheduleTypeText(form.type) : "课程", + isFund: moduleKey === "fund", + isFundIncome: form.type === "income", + isFundExpense: form.type === "expense", + fundIncomeClass: form.type === "income" ? "active income" : "", + fundExpenseClass: form.type === "expense" ? "active expense" : "", + fundCategories: form.type === "income" + ? this.data.fundIncomeCategories + : this.data.fundExpenseCategories }); wx.setNavigationBarTitle({ title: `新增${module.title}` }); }, @@ -60,7 +90,62 @@ Page({ onPicker(event) { const field = event.currentTarget.dataset.field; const value = event.currentTarget.dataset.values.split(",")[Number(event.detail.value)]; - this.setData({ [`form.${field}`]: value }); + const nextData = { [`form.${field}`]: value }; + if (this.data.moduleKey === "schedule" && field === "type") { + nextData.isScheduleDeadline = value === "deadline"; + if (value === "deadline") { + nextData["form.end_time"] = ""; + } + } + this.setData(nextData); + }, + + scheduleTypeText(type) { + return { + course: "课程", + deadline: "截止日", + activity: "活动" + }[type] || type || "课程"; + }, + + onScheduleTypeChange(event) { + const index = Number(event.detail.value); + const value = this.data.scheduleTypeValues[index]; + const nextData = { + "form.type": value, + scheduleTypeLabel: this.scheduleTypeText(value), + isScheduleDeadline: value === "deadline", + scheduleTimeLabel: value === "deadline" ? "截止时间" : "开始时间" + }; + if (value === "deadline") { + nextData["form.end_time"] = ""; + } + this.setData(nextData); + }, + + setFundType(event) { + const type = event.currentTarget.dataset.type; + const categories = type === "income" + ? this.data.fundIncomeCategories + : this.data.fundExpenseCategories; + this.setData({ + "form.type": type, + "form.category": categories[0], + isFundIncome: type === "income", + isFundExpense: type === "expense", + fundIncomeClass: type === "income" ? "active income" : "", + fundExpenseClass: type === "expense" ? "active expense" : "", + fundCategories: categories + }); + }, + + onDateChange(event) { + this.setData({ "form.record_date": event.detail.value }); + }, + + onFundCategoryChange(event) { + const index = Number(event.detail.value); + this.setData({ "form.category": this.data.fundCategories[index] }); }, async submit() { @@ -91,15 +176,40 @@ Page({ }); } if (moduleKey === "schedule") { + if (!form.title || !form.start_time) { + wx.showToast({ title: "请填写标题和开始时间", icon: "none" }); + return; + } + if (form.type !== "deadline" && !form.end_time) { + wx.showToast({ title: "请填写结束时间", icon: "none" }); + return; + } + if (form.end_time && new Date(form.end_time).getTime() <= new Date(form.start_time).getTime()) { + wx.showToast({ title: "结束时间应晚于开始时间", icon: "none" }); + return; + } await post(`/api/schedule/?class_id=${classId}`, { title: form.title, type: form.type, start_time: form.start_time, + end_time: form.end_time || null, location: form.location || null, description: form.description || null }); } if (moduleKey === "fund") { + if (!form.amount || Number(form.amount) <= 0) { + wx.showToast({ title: "请输入有效金额", icon: "none" }); + return; + } + if (!form.category) { + wx.showToast({ title: "请输入分类", icon: "none" }); + return; + } + if (!form.record_date) { + wx.showToast({ title: "请选择日期", icon: "none" }); + return; + } await post(`/api/fund/?class_id=${classId}`, { type: form.type, amount: Number(form.amount), diff --git a/miniprogram/pages/manage/index.json b/miniprogram/pages/manage/index.json new file mode 100644 index 0000000..9359893 --- /dev/null +++ b/miniprogram/pages/manage/index.json @@ -0,0 +1,3 @@ +{ + "navigationBarTitleText": "新增" +} diff --git a/miniprogram/pages/manage/index.wxml b/miniprogram/pages/manage/index.wxml index 2bcc557..7b0e141 100644 --- a/miniprogram/pages/manage/index.wxml +++ b/miniprogram/pages/manage/index.wxml @@ -1,54 +1,114 @@ - - {{title}} - - - 标题 - + + + CLASS MANAGEMENT + {{title}} + 填写必要信息后保存,内容会同步到当前班级。 - - 内容 -