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}}
-
-
- 标题
-
+
+
-
- 内容
-
- 置顶
-
+
+
+ 标题
+
+
-
- 说明
-
- 选项,每行一个
-
- 匿名
-
+
+
+ 内容
+
+
+
+
+ 置顶公告
+ 开启后会优先显示在首页
+
+
+
-
- 类型
-
- {{form.type}}
-
- 开始时间,格式:2026-05-07T09:00:00
-
- 地点
-
- 说明
-
+
+
+ 说明
+
+
+
+
+ 选项
+ 每行一个
+
+
+
+
+
+ 匿名投票
+ 同学参与后不展示个人选择
+
+
+
-
- 类型
-
- {{form.type}}
-
- 金额
-
- 分类
-
- 日期,格式:2026-05-07
-
- 说明
-
+
+
+ 类型
+
+ {{scheduleTypeLabel}}
+
+
+
+
+ {{scheduleTimeLabel}}
+ 格式:2026-05-07T09:00:00
+
+
+
+
+
+ 结束时间
+ 课程/活动必填
+
+
+
+
+ 地点
+
+
+
+ 说明
+
+
-
+
+
+ 收支类型
+
+ 入账
+ 出账
+
+
+
+ 金额
+
+
+
+ 小类型
+
+ {{form.category}}
+
+
+
+ 日期
+
+ {{form.record_date}}
+
+
+
+ 备注
+
+
+
+
+
+
+
diff --git a/miniprogram/pages/manage/index.wxss b/miniprogram/pages/manage/index.wxss
index 090a59f..99530aa 100644
--- a/miniprogram/pages/manage/index.wxss
+++ b/miniprogram/pages/manage/index.wxss
@@ -1,12 +1,32 @@
-input {
+.fund-type-switch {
+ display: flex;
+ gap: 12rpx;
margin-top: 12rpx;
- height: 72rpx;
- font-size: 30rpx;
+ padding: 8rpx;
+ border-radius: 24rpx;
+ background: #f1e4d4;
}
-textarea {
- width: 100%;
- min-height: 160rpx;
- margin-top: 12rpx;
- font-size: 28rpx;
+.fund-type {
+ flex: 1;
+ height: 72rpx;
+ border-radius: 20rpx;
+ color: #7f7065;
+ font-size: 27rpx;
+ font-weight: 700;
+ line-height: 72rpx;
+ text-align: center;
+}
+
+.fund-type.active {
+ background: #fffaf3;
+ color: #6b1f2b;
+}
+
+.fund-type.income {
+ color: #1f7a4d;
+}
+
+.fund-type.expense {
+ color: #9a3a2f;
}
diff --git a/miniprogram/pages/mine/index.js b/miniprogram/pages/mine/index.js
index 62418c8..6cb3344 100644
--- a/miniprogram/pages/mine/index.js
+++ b/miniprogram/pages/mine/index.js
@@ -12,10 +12,11 @@ Page({
const classRes = await get("/api/classes/");
const app = getApp();
const activeClassId = app.globalData.activeClassId;
- const activeMembership = user.memberships?.find((item) => item.class_id === activeClassId) || user.active_membership;
+ const memberships = Array.isArray(user.memberships) ? user.memberships : [];
+ const activeMembership = memberships.find((item) => item.class_id === activeClassId) || user.active_membership;
this.setData({
user,
- activeClassName: activeMembership?.class_name || "",
+ activeClassName: (activeMembership && activeMembership.class_name) || "",
classes: (classRes.items || []).map((item) => ({
...item,
is_active: item.id === activeClassId
diff --git a/miniprogram/pages/module/index.js b/miniprogram/pages/module/index.js
index 6639d38..2d9d5e5 100644
--- a/miniprogram/pages/module/index.js
+++ b/miniprogram/pages/module/index.js
@@ -9,11 +9,33 @@ const ENDPOINTS = {
timeline: "/api/timeline/",
votes: "/api/votes/",
schedule: "/api/schedule/",
- resources: "/api/resources/",
- reading_corner: "/api/reading/feed",
fund: "/api/fund/"
};
+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: {
moduleKey: "",
@@ -25,6 +47,9 @@ Page({
isTimeline: false,
isDirectory: false,
isSchedule: false,
+ isFund: false,
+ isVotes: false,
+ fundStats: null,
needsRefresh: false,
loading: false
},
@@ -34,24 +59,28 @@ Page({
const module = getModule(moduleKey);
const app = getApp();
const classId = getActiveClassId();
+ const title = module ? module.title : "功能";
+ const icon = module ? module.icon : "项";
this.setData({
moduleKey,
- title: module?.title || "功能",
- moduleIcon: module?.icon || "项",
+ title,
+ moduleIcon: icon,
isTimeline: moduleKey === "timeline",
isDirectory: moduleKey === "directory",
isSchedule: moduleKey === "schedule",
+ isFund: moduleKey === "fund",
+ isVotes: moduleKey === "votes",
canPostTimeline: moduleKey === "timeline",
canManage: ["announcements", "votes", "schedule", "fund"].includes(moduleKey) &&
hasManagePermission(app.globalData.user, classId, moduleKey)
});
- wx.setNavigationBarTitle({ title: module?.title || "功能" });
+ wx.setNavigationBarTitle({ title });
if (!module || !ensureModuleOpen(moduleKey)) return;
this.load();
},
onShow() {
- if (this.data.moduleKey) {
+ if (this.data.moduleKey && getModule(this.data.moduleKey)) {
this.setData({ needsRefresh: false });
this.load();
}
@@ -68,18 +97,40 @@ Page({
if (!endpoint) return;
this.setData({ loading: true });
try {
+ let stats = null;
+ if (this.data.moduleKey === "fund") {
+ stats = await get("/api/fund/statistics", { class_id: classId });
+ }
const res = await get(endpoint, { page_size: 20, class_id: classId });
const rawItems = Array.isArray(res) ? res : res.items || [];
- const currentUserId = getApp().globalData.user?.id;
+ const currentUser = getApp().globalData.user || {};
+ const currentUserId = currentUser.id;
+ const formatAmount = (value) => Number(value || 0).toFixed(2);
const items = rawItems.map((item) => ({
...item,
can_delete: this.data.moduleKey === "timeline" && item.author_id === currentUserId,
initial: String(item.name || item.author_name || this.data.title || "项").slice(0, 1),
schedule_day: item.start_time ? String(item.start_time).slice(8, 10) : "",
schedule_month: item.start_time ? `${String(item.start_time).slice(5, 7)}月` : "",
- committee_text: item.committee_role ? ` · ${item.committee_role}` : ""
+ schedule_time_text: this.data.moduleKey === "schedule" ? formatScheduleTime(item) : "",
+ schedule_type_text: this.data.moduleKey === "schedule" ? scheduleTypeText(item.type) : "",
+ vote_status_text: item.status === "open" ? "进行中" : "已关闭",
+ vote_type_text: item.vote_type === "multiple" ? `多选,最多 ${item.max_choices || 1} 项` : "单选",
+ vote_action_text: item.has_voted ? "已参与" : "待参与",
+ vote_pill_class: item.has_voted ? "done" : "",
+ vote_options_text: Array.isArray(item.options) ? `${item.options.length} 个选项` : "",
+ committee_text: item.committee_role ? ` · ${item.committee_role}` : "",
+ fund_type_text: item.type === "income" ? "收入" : "支出",
+ fund_type_class: item.type === "income" ? "income" : "expense",
+ amount_text: formatAmount(item.amount)
}));
- this.setData({ items });
+ const fundStats = stats ? {
+ ...stats,
+ total_income_text: formatAmount(stats.total_income),
+ total_expense_text: formatAmount(stats.total_expense),
+ balance_text: formatAmount(stats.balance)
+ } : null;
+ this.setData({ items, fundStats });
} catch (error) {
if (error.message === "该功能当前未开放") {
wx.redirectTo({
@@ -109,13 +160,16 @@ Page({
wx.navigateTo({ url: `/pages/timeline-detail/index?id=${id}` });
return;
}
+ if (key === "votes") {
+ wx.navigateTo({ url: `/pages/vote-detail/index?id=${id}` });
+ }
},
previewImage(event) {
const current = event.currentTarget.dataset.src;
const postId = Number(event.currentTarget.dataset.postId);
const post = this.data.items.find((item) => item.id === postId);
- const urls = post?.image_urls || [];
+ const urls = post && post.image_urls ? post.image_urls : [];
if (!current || !urls.length) return;
wx.previewImage({ current, urls });
},
@@ -152,5 +206,15 @@ Page({
openTimelineCreate() {
wx.navigateTo({ url: "/pages/timeline-create/index" });
+ },
+
+ openCreate() {
+ if (this.data.canPostTimeline) {
+ this.openTimelineCreate();
+ return;
+ }
+ if (this.data.canManage) {
+ this.openManage();
+ }
}
});
diff --git a/miniprogram/pages/module/index.wxml b/miniprogram/pages/module/index.wxml
index c6baf13..e3b13c8 100644
--- a/miniprogram/pages/module/index.wxml
+++ b/miniprogram/pages/module/index.wxml
@@ -4,9 +4,31 @@
{{title}}
当前内容来自已开放的班级模块,关闭后会自动隐藏入口。
-
-
+
+
+
+ 班费汇总
+
+
+
+ 收入
+ ¥{{fundStats.total_income_text}}
+
+
+ 支出
+ ¥{{fundStats.total_expense_text}}
+
+
+ 结余
+ ¥{{fundStats.balance_text}}
+
+
+
+
+
+ 班费明细
+
@@ -44,9 +66,35 @@
{{item.title}}
{{item.location || "地点待定"}}
- {{item.start_time}}
+ {{item.schedule_time_text}}
+
+ {{item.schedule_type_text}}
+
+
+
+ 选
+
+ {{item.title}}
+ {{item.description}}
+
+ {{item.vote_type_text}}
+ {{item.vote_options_text}}
+ {{item.total_voters}} 人参与
+
+
+ {{item.vote_action_text}}
+
+
+
+ {{item.fund_type_text}}
+
+
+ {{item.category}}
+ ¥{{item.amount_text}}
+
+ {{item.description || "无备注"}}
+ {{item.recorder_name}} · {{item.record_date}}
- {{item.type}}
@@ -61,4 +109,6 @@
暂无内容
+
+
diff --git a/miniprogram/pages/profile-edit/index.js b/miniprogram/pages/profile-edit/index.js
index 8a29ea8..9a3756b 100644
--- a/miniprogram/pages/profile-edit/index.js
+++ b/miniprogram/pages/profile-edit/index.js
@@ -4,6 +4,20 @@ const { showError } = require("../../utils/page-helpers");
Page({
data: {
+ industryOptions: [
+ "金融",
+ "科技",
+ "咨询",
+ "医疗健康",
+ "教育",
+ "房地产",
+ "制造业",
+ "消费零售",
+ "能源",
+ "传媒",
+ "政府/公共事业",
+ "其他"
+ ],
form: {
name: "",
industry: "",
@@ -35,6 +49,11 @@ Page({
this.setData({ [`form.${field}`]: event.detail.value });
},
+ onIndustryChange(event) {
+ const index = Number(event.detail.value);
+ this.setData({ "form.industry": this.data.industryOptions[index] });
+ },
+
async save() {
this.setData({ loading: true });
try {
diff --git a/miniprogram/pages/profile-edit/index.wxml b/miniprogram/pages/profile-edit/index.wxml
index dac3b07..97de5ce 100644
--- a/miniprogram/pages/profile-edit/index.wxml
+++ b/miniprogram/pages/profile-edit/index.wxml
@@ -1,18 +1,42 @@
-
-
- PROFILE
- 编辑个人资料
- 这些信息会在成员名录中展示给同班同学。
+
+
- 姓名
- 行业
- 公司
- 职位
- 微信号
- 简介
+
+
+ 姓名
+
+
+
+ 行业
+
+ {{form.industry || "请选择行业"}}
+
+
+
+ 公司
+
+
+
+ 职位
+
+
+
+ 微信号
+
+
+
+ 简介
+
+
+
-
+
+
+
diff --git a/miniprogram/pages/profile-edit/index.wxss b/miniprogram/pages/profile-edit/index.wxss
index 02c4b82..d051237 100644
--- a/miniprogram/pages/profile-edit/index.wxss
+++ b/miniprogram/pages/profile-edit/index.wxss
@@ -1,12 +1,7 @@
-input {
- margin-top: 12rpx;
- height: 72rpx;
- font-size: 30rpx;
+.section {
+ margin-top: 0;
}
-textarea {
- width: 100%;
- min-height: 180rpx;
- margin-top: 12rpx;
- font-size: 28rpx;
+.profile-bio {
+ min-height: 210rpx;
}
diff --git a/miniprogram/pages/schedule-detail/index.js b/miniprogram/pages/schedule-detail/index.js
index 3f253e8..bf00bb3 100644
--- a/miniprogram/pages/schedule-detail/index.js
+++ b/miniprogram/pages/schedule-detail/index.js
@@ -1,6 +1,19 @@
const { get } = require("../../utils/api");
const { showError } = require("../../utils/page-helpers");
+function formatDateTime(value) {
+ if (!value) return "";
+ return String(value).replace("T", " ").slice(0, 16);
+}
+
+function scheduleTypeText(type) {
+ return {
+ course: "课程",
+ deadline: "截止日",
+ activity: "活动"
+ }[type] || type || "排期";
+}
+
Page({
data: { item: null, loading: false },
@@ -14,7 +27,15 @@ Page({
this.setData({ loading: true });
try {
const item = await get(`/api/schedule/${id}`);
- this.setData({ item });
+ this.setData({
+ item: {
+ ...item,
+ start_time_text: formatDateTime(item.start_time),
+ end_time_text: formatDateTime(item.end_time),
+ type_text: scheduleTypeText(item.type),
+ start_label: item.type === "deadline" ? "截止时间" : "开始时间"
+ }
+ });
} catch (error) {
showError(error, "加载排期失败");
} finally {
diff --git a/miniprogram/pages/schedule-detail/index.wxml b/miniprogram/pages/schedule-detail/index.wxml
index 49dfc2a..ed416bb 100644
--- a/miniprogram/pages/schedule-detail/index.wxml
+++ b/miniprogram/pages/schedule-detail/index.wxml
@@ -11,7 +11,7 @@
类
类型
- {{item.type}}
+ {{item.type_text}}
@@ -19,8 +19,8 @@
始
- 开始时间
- {{item.start_time}}
+ {{item.start_label}}
+ {{item.start_time_text}}
@@ -29,7 +29,7 @@
止
结束时间
- {{item.end_time}}
+ {{item.end_time_text}}
diff --git a/miniprogram/pages/timeline-create/index.js b/miniprogram/pages/timeline-create/index.js
index 40934ec..667d050 100644
--- a/miniprogram/pages/timeline-create/index.js
+++ b/miniprogram/pages/timeline-create/index.js
@@ -61,7 +61,7 @@ Page({
wx.showToast({ title: "已发布", icon: "success" });
const pages = getCurrentPages();
const previousPage = pages[pages.length - 2];
- if (previousPage?.setData) {
+ if (previousPage && previousPage.setData) {
previousPage.setData({ needsRefresh: true });
}
setTimeout(() => wx.navigateBack(), 500);
diff --git a/miniprogram/pages/timeline-create/index.wxml b/miniprogram/pages/timeline-create/index.wxml
index 7bc5af7..6ebdea5 100644
--- a/miniprogram/pages/timeline-create/index.wxml
+++ b/miniprogram/pages/timeline-create/index.wxml
@@ -1,20 +1,24 @@
-
-
- CLASS FEED
- 发布班级动态
- 分享课程现场、活动照片、学习收获或班级提醒。
+
+
-
-
- 标题
-
+
+
+
+ 标题
+
+
-
- 正文
-
+
+
+ 正文
+
+
-
+
图片
添加
@@ -29,5 +33,7 @@
-
+
+
+
diff --git a/miniprogram/pages/timeline-create/index.wxss b/miniprogram/pages/timeline-create/index.wxss
index c35c8e0..5e38334 100644
--- a/miniprogram/pages/timeline-create/index.wxss
+++ b/miniprogram/pages/timeline-create/index.wxss
@@ -1,14 +1,9 @@
-input {
- margin-top: 12rpx;
- height: 72rpx;
- font-size: 30rpx;
+.form-section {
+ margin-top: 0;
}
-textarea {
- width: 100%;
- min-height: 220rpx;
- margin-top: 12rpx;
- font-size: 28rpx;
+.timeline-content {
+ min-height: 250rpx;
}
.image-grid {
diff --git a/miniprogram/pages/timeline-detail/index.js b/miniprogram/pages/timeline-detail/index.js
index f1681a1..85af290 100644
--- a/miniprogram/pages/timeline-detail/index.js
+++ b/miniprogram/pages/timeline-detail/index.js
@@ -33,9 +33,10 @@ Page({
get(`/api/timeline/${this.data.id}`),
get(`/api/timeline/${this.data.id}/comments`, { page_size: 50 })
]);
+ const currentUser = getApp().globalData.user || {};
this.setData({
post: postDetail,
- canDelete: postDetail.author_id === getApp().globalData.user?.id,
+ canDelete: postDetail.author_id === currentUser.id,
comments: (commentsRes.items || []).map((item) => ({
...item,
initial: String(item.author_name || "评").slice(0, 1)
@@ -67,7 +68,7 @@ Page({
previewImage(event) {
const current = event.currentTarget.dataset.src;
- const urls = this.data.post?.image_urls || [];
+ const urls = this.data.post && this.data.post.image_urls ? this.data.post.image_urls : [];
if (!current || !urls.length) return;
wx.previewImage({ current, urls });
},
@@ -89,7 +90,7 @@ Page({
wx.showToast({ title: "已删除", icon: "success" });
const pages = getCurrentPages();
const previousPage = pages[pages.length - 2];
- if (previousPage?.setData) previousPage.setData({ needsRefresh: true });
+ if (previousPage && previousPage.setData) previousPage.setData({ needsRefresh: true });
setTimeout(() => wx.navigateBack(), 500);
} catch (error) {
showError(error, "删除失败");
diff --git a/miniprogram/pages/vote-detail/index.js b/miniprogram/pages/vote-detail/index.js
new file mode 100644
index 0000000..51b22a8
--- /dev/null
+++ b/miniprogram/pages/vote-detail/index.js
@@ -0,0 +1,107 @@
+const { get, post } = require("../../utils/api");
+const { showError } = require("../../utils/page-helpers");
+
+function formatDateTime(value) {
+ if (!value) return "";
+ return String(value).replace("T", " ").slice(0, 16);
+}
+
+function normalizeVote(item, selectedIds) {
+ const optionIds = selectedIds || item.my_option_ids || [];
+ const totalVotes = (item.options || []).reduce((sum, option) => sum + Number(option.vote_count || 0), 0);
+ return {
+ ...item,
+ status_text: item.status === "open" ? "进行中" : "已关闭",
+ vote_type_text: item.vote_type === "multiple" ? `多选,最多 ${item.max_choices || 1} 项` : "单选",
+ deadline_text: item.deadline ? formatDateTime(item.deadline) : "",
+ can_submit: item.status === "open" && !item.has_voted,
+ voted_action_text: item.has_voted ? "已参与" : "请选择",
+ submit_text: item.has_voted ? "你已参与" : "投票已关闭",
+ selectedIds: optionIds,
+ options: (item.options || []).map((option) => {
+ const voteCount = Number(option.vote_count || 0);
+ const percent = totalVotes > 0 ? Math.round((voteCount / totalVotes) * 100) : 0;
+ return {
+ ...option,
+ checked: optionIds.includes(option.id),
+ selected_class: optionIds.includes(option.id) ? "selected" : "",
+ check_text: optionIds.includes(option.id) ? "✓" : "",
+ percent,
+ percent_style: `width: ${percent}%`,
+ voter_names_text: Array.isArray(option.voter_names) ? option.voter_names.join("、") : ""
+ };
+ })
+ };
+}
+
+Page({
+ data: {
+ id: null,
+ item: null,
+ loading: false,
+ submitting: false
+ },
+
+ onLoad(options) {
+ wx.setNavigationBarTitle({ title: "投票详情" });
+ this.setData({ id: options.id || null });
+ this.load(options.id);
+ },
+
+ async onPullDownRefresh() {
+ await this.load(this.data.id);
+ wx.stopPullDownRefresh();
+ },
+
+ async load(id) {
+ if (!id) return;
+ this.setData({ loading: true });
+ try {
+ const item = await get(`/api/votes/${id}`);
+ this.setData({ item: normalizeVote(item) });
+ } catch (error) {
+ showError(error, "加载投票失败");
+ } finally {
+ this.setData({ loading: false });
+ }
+ },
+
+ toggleOption(event) {
+ const optionId = Number(event.currentTarget.dataset.id);
+ const item = this.data.item;
+ if (!item || item.status !== "open" || item.has_voted) return;
+ let selectedIds = item.selectedIds || [];
+ if (item.vote_type === "single") {
+ selectedIds = [optionId];
+ } else if (selectedIds.includes(optionId)) {
+ selectedIds = selectedIds.filter((id) => id !== optionId);
+ } else {
+ if (selectedIds.length >= Number(item.max_choices || 1)) {
+ wx.showToast({ title: `最多选择 ${item.max_choices} 项`, icon: "none" });
+ return;
+ }
+ selectedIds = [...selectedIds, optionId];
+ }
+ this.setData({ item: normalizeVote(item, selectedIds) });
+ },
+
+ async submit() {
+ const item = this.data.item;
+ if (!item || item.has_voted || item.status !== "open") return;
+ const selectedIds = item.selectedIds || [];
+ if (!selectedIds.length) {
+ wx.showToast({ title: "请选择投票选项", icon: "none" });
+ return;
+ }
+ this.setData({ submitting: true });
+ try {
+ await post(`/api/votes/${item.id}/submit`, { option_ids: selectedIds });
+ wx.showToast({ title: "已投票", icon: "success" });
+ await this.load(item.id);
+ } catch (error) {
+ showError(error, "投票失败");
+ } finally {
+ this.setData({ submitting: false });
+ }
+ }
+});
diff --git a/miniprogram/pages/vote-detail/index.json b/miniprogram/pages/vote-detail/index.json
new file mode 100644
index 0000000..edb488f
--- /dev/null
+++ b/miniprogram/pages/vote-detail/index.json
@@ -0,0 +1,4 @@
+{
+ "navigationBarTitleText": "投票详情",
+ "enablePullDownRefresh": true
+}
diff --git a/miniprogram/pages/vote-detail/index.wxml b/miniprogram/pages/vote-detail/index.wxml
new file mode 100644
index 0000000..994289c
--- /dev/null
+++ b/miniprogram/pages/vote-detail/index.wxml
@@ -0,0 +1,56 @@
+
+
+ VOTE
+ {{item.title}}
+ {{item.description || "请选择你的意见。"}}
+
+
+ {{item.total_voters}}
+ 参与人数
+
+
+ {{item.options.length}}
+ 选项
+
+
+ {{item.status_text}}
+ {{item.vote_type_text}}
+
+
+
+
+
+
+ 选项
+ {{item.voted_action_text}}
+
+
+
+ {{item.check_text}}
+
+ {{item.content}}
+ {{item.vote_count}} 票 · {{item.percent}}%
+
+
+
+
+
+ {{item.voter_names_text}}
+
+
+
+
+
+ 止
+
+ 截止时间
+ {{item.deadline_text}}
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/pages/vote-detail/index.wxss b/miniprogram/pages/vote-detail/index.wxss
new file mode 100644
index 0000000..01c88c3
--- /dev/null
+++ b/miniprogram/pages/vote-detail/index.wxss
@@ -0,0 +1,57 @@
+.page {
+ padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
+}
+
+.vote-option {
+ margin-bottom: 18rpx;
+ border: 1rpx solid rgba(121, 84, 54, 0.12);
+ border-radius: 28rpx;
+ background: rgba(255, 252, 247, 0.96);
+ padding: 24rpx;
+ box-shadow: 0 14rpx 36rpx rgba(68, 39, 27, 0.06);
+}
+
+.vote-option.selected {
+ border-color: rgba(107, 31, 43, 0.42);
+ background: #fff8ed;
+}
+
+.vote-option-main {
+ display: flex;
+ align-items: center;
+ gap: 18rpx;
+}
+
+.vote-check {
+ flex: none;
+ width: 48rpx;
+ height: 48rpx;
+ border: 2rpx solid rgba(107, 31, 43, 0.28);
+ border-radius: 999rpx;
+ color: #6b1f2b;
+ font-size: 28rpx;
+ font-weight: 800;
+ line-height: 46rpx;
+ text-align: center;
+}
+
+.vote-progress {
+ overflow: hidden;
+ height: 10rpx;
+ margin-top: 18rpx;
+ border-radius: 999rpx;
+ background: #f0dfcd;
+}
+
+.vote-progress-fill {
+ height: 100%;
+ border-radius: 999rpx;
+ background: #6b1f2b;
+}
+
+.vote-voters {
+ margin-top: 14rpx;
+ color: #8a7b70;
+ font-size: 22rpx;
+ line-height: 1.45;
+}
diff --git a/miniprogram/utils/api.js b/miniprogram/utils/api.js
index 3b19100..9f6a3c2 100644
--- a/miniprogram/utils/api.js
+++ b/miniprogram/utils/api.js
@@ -28,7 +28,8 @@ function request(path, options = {}) {
return;
}
if (res.statusCode >= 400) {
- const message = res.data?.detail || res.data?.message || "操作失败";
+ const data = res.data || {};
+ const message = data.detail || data.message || "操作失败";
reject(new Error(message));
return;
}
diff --git a/miniprogram/utils/auth.js b/miniprogram/utils/auth.js
index 873c9b4..e3eac6c 100644
--- a/miniprogram/utils/auth.js
+++ b/miniprogram/utils/auth.js
@@ -8,7 +8,7 @@ function saveSession(token, user) {
wx.setStorageSync("auth_token", token);
wx.setStorageSync("auth_user", user);
const app = getApp();
- if (app?.setUser) app.setUser(user);
+ if (app && app.setUser) app.setUser(user);
}
function requireLogin() {
@@ -23,7 +23,7 @@ async function refreshMe() {
const user = await get("/api/auth/me");
wx.setStorageSync("auth_user", user);
const app = getApp();
- if (app?.setUser) app.setUser(user);
+ if (app && app.setUser) app.setUser(user);
return user;
}
diff --git a/miniprogram/utils/config.js b/miniprogram/utils/config.js
index 7c7fd97..ac625cc 100644
--- a/miniprogram/utils/config.js
+++ b/miniprogram/utils/config.js
@@ -1,4 +1,4 @@
module.exports = {
// 发布前改为后端 HTTPS 域名,例如 https://classhub.example.com
- apiBase: "http://127.0.0.1:8000"
+ apiBase: "https://hkuicb.info"
};
diff --git a/miniprogram/utils/modules.js b/miniprogram/utils/modules.js
index fea266f..61b5d2b 100644
--- a/miniprogram/utils/modules.js
+++ b/miniprogram/utils/modules.js
@@ -2,11 +2,9 @@ const MODULES = {
announcements: { key: "announcements", title: "公告", desc: "查看班级重要通知", group: "class", icon: "告" },
schedule: { key: "schedule", title: "排期", desc: "课程、活动与截止日", group: "class", icon: "日" },
directory: { key: "directory", title: "成员名录", desc: "查找同学与班委", group: "class", icon: "友" },
- resources: { key: "resources", title: "资源库", desc: "浏览和下载班级文件", group: "class", icon: "档" },
fund: { key: "fund", title: "班费", desc: "查看公开收支账本", group: "class", icon: "账" },
timeline: { key: "timeline", title: "班级动态", desc: "分享近况与评论互动", group: "interact", icon: "动" },
- votes: { key: "votes", title: "投票", desc: "参与班级决策", group: "interact", icon: "选" },
- reading_corner: { key: "reading_corner", title: "读书角", desc: "记录阅读与笔记", group: "interact", icon: "书" }
+ votes: { key: "votes", title: "投票", desc: "参与班级决策", group: "interact", icon: "选" }
};
const MINI_PROGRAM_MODULE_KEYS = Object.keys(MODULES);
diff --git a/miniprogram/utils/page-helpers.js b/miniprogram/utils/page-helpers.js
index 3ce53a0..2067ee8 100644
--- a/miniprogram/utils/page-helpers.js
+++ b/miniprogram/utils/page-helpers.js
@@ -3,13 +3,16 @@ const { getModule, isModuleEnabled } = require("./modules");
function getActiveClassId() {
const app = getApp();
const user = app.globalData.user || wx.getStorageSync("auth_user");
- return app.globalData.activeClassId || user?.active_membership?.class_id || user?.memberships?.[0]?.class_id || null;
+ const memberships = user && Array.isArray(user.memberships) ? user.memberships : [];
+ const activeMembership = user && user.active_membership ? user.active_membership : null;
+ const firstMembership = memberships.length ? memberships[0] : null;
+ return app.globalData.activeClassId || (activeMembership && activeMembership.class_id) || (firstMembership && firstMembership.class_id) || null;
}
function getEnabledModules() {
const app = getApp();
const user = app.globalData.user || wx.getStorageSync("auth_user");
- return app.globalData.enabledModules || user?.enabled_modules || null;
+ return app.globalData.enabledModules || (user && user.enabled_modules) || null;
}
function getActiveClassName() {
@@ -17,24 +20,27 @@ function getActiveClassName() {
const user = app.globalData.user || wx.getStorageSync("auth_user");
const classId = getActiveClassId();
const savedClass = wx.getStorageSync("active_class") || null;
- if (savedClass?.id === classId && savedClass?.name) return savedClass.name;
- const membership = user?.memberships?.find((item) => item.class_id === classId);
- return membership?.class_name || user?.active_membership?.class_name || "HKU ICB";
+ if (savedClass && savedClass.id === classId && savedClass.name) return savedClass.name;
+ const memberships = user && Array.isArray(user.memberships) ? user.memberships : [];
+ const membership = memberships.find((item) => item.class_id === classId);
+ const activeMembership = user && user.active_membership ? user.active_membership : null;
+ return (membership && membership.class_name) || (activeMembership && activeMembership.class_name) || "HKU ICB";
}
function ensureModuleOpen(moduleKey) {
const enabledModules = getEnabledModules();
if (isModuleEnabled(moduleKey, enabledModules)) return true;
const module = getModule(moduleKey);
+ const title = module ? module.title : "功能";
wx.redirectTo({
- url: `/pages/module-unavailable/index?title=${encodeURIComponent(module?.title || "功能")}`
+ url: `/pages/module-unavailable/index?title=${encodeURIComponent(title)}`
});
return false;
}
function showError(error, fallback = "加载失败") {
wx.showToast({
- title: error?.message || fallback,
+ title: (error && error.message) || fallback,
icon: "none"
});
}
diff --git a/miniprogram/utils/permissions.js b/miniprogram/utils/permissions.js
index dd17d11..8c29e03 100644
--- a/miniprogram/utils/permissions.js
+++ b/miniprogram/utils/permissions.js
@@ -23,14 +23,19 @@ const TEACHER_DEFAULT_PERMISSIONS = new Set([
]);
function activeMembership(user, classId) {
- return user?.memberships?.find((item) => item.class_id === classId) || user?.active_membership || null;
+ if (!user) return null;
+ const memberships = Array.isArray(user.memberships) ? user.memberships : [];
+ return memberships.find((item) => item.class_id === classId) || user.active_membership || null;
}
function hasManagePermission(user, classId, moduleKey) {
const permission = MODULE_MANAGE_PERMISSIONS[moduleKey];
if (!permission || !user) return false;
if (user.role === "super_admin") return true;
- const membershipPermissions = activeMembership(user, classId)?.class_permissions || [];
+ const membership = activeMembership(user, classId);
+ const membershipPermissions = membership && Array.isArray(membership.class_permissions)
+ ? membership.class_permissions
+ : [];
if (user.role === "teacher") {
return TEACHER_DEFAULT_PERMISSIONS.has(permission) || membershipPermissions.includes(permission);
}