From 5b402c35323334e7e4b017cfd2d8e5b4033df0c1 Mon Sep 17 00:00:00 2001 From: kola-web Date: Mon, 22 Jun 2026 20:19:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=EF=BC=8C=E4=BC=98=E5=8C=96=E6=B4=BB=E5=8A=A8?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E4=B8=8E=E9=A1=B5=E9=9D=A2=E7=BB=86=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增通用分页组件pagination,支持空状态、加载中、无更多数据状态 - 重构活动列表页面,添加分页逻辑、分类筛选、动态数据渲染 - 完善活动详情页面,支持动态数据展示、签到报名逻辑 - 优化活动创建页面,调整上传组件与表单逻辑 - 重构登录与请求逻辑,统一鉴权处理 - 简化课表页面代码,移除旧的双向滚动逻辑 - 优化上传组件,移除预览删除功能,仅保留上传核心逻辑 - 新增活动结果页面,完善报名成功后的展示与推荐活动 --- AGENTS.md | 2 + src/api/request.ts | 5 +- src/app.ts | 145 +++----- src/components/pagination/index.js | 26 ++ src/components/pagination/index.json | 6 + src/components/pagination/index.scss | 6 + src/components/pagination/index.wxml | 9 + src/components/uploadFile/README.md | 34 +- src/components/uploadFile/index.scss | 206 +----------- src/components/uploadFile/index.ts | 179 +++------- src/components/uploadFile/index.wxml | 98 +----- src/images/none.png | Bin 0 -> 78543 bytes src/pages/act/index.json | 3 +- src/pages/act/index.scss | 17 +- src/pages/act/index.ts | 485 +++++++++++++++++++++++++-- src/pages/act/index.wxml | 87 +++-- src/pages/actAdd/index.json | 3 +- src/pages/actAdd/index.scss | 30 ++ src/pages/actAdd/index.ts | 618 ++++++++++++++++++++++++++++++++--- src/pages/actAdd/index.wxml | 75 +++-- src/pages/actAddResult/index.ts | 52 ++- src/pages/actAddResult/index.wxml | 4 +- src/pages/actDetail/index.scss | 33 +- src/pages/actDetail/index.ts | 501 +++++++++++++++++++++++++++- src/pages/actDetail/index.wxml | 153 +++++---- src/pages/actResult/index.ts | 85 ++++- src/pages/actResult/index.wxml | 26 +- src/pages/login/index.ts | 12 +- src/pages/schedule/index.scss | 220 +++++++------ src/pages/schedule/index.ts | 26 -- src/pages/schedule/index.wxml | 47 +-- typings/index.d.ts | 12 +- 32 files changed, 2224 insertions(+), 981 deletions(-) create mode 100644 src/components/pagination/index.js create mode 100644 src/components/pagination/index.json create mode 100644 src/components/pagination/index.scss create mode 100644 src/components/pagination/index.wxml create mode 100644 src/images/none.png diff --git a/AGENTS.md b/AGENTS.md index ddea265..41d4b0c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,7 @@ WeChat Mini Program (微信小程序) — school campus app. Source root is `src - **Custom Page wrapper** (`src/utils/page.ts`): The global `Page` constructor is replaced in `app.ts` `onLaunch`. Every page automatically gets navbar setup, image params, scroll-based nav background, and a default `onShareAppMessage`. When adding page lifecycle hooks, be aware these wrappers run before your own handlers. - **`wx.ajax`**: Custom property added to `wx` in `app.ts` `onLaunch`. It is a curried version of `src/api/request.ts` with the base URL baked in. Use `wx.ajax(...)` for network requests — do not call `request` directly from pages. - **Login flow**: Managed in `App` via `globalData.loginState` / `globalData.initLoginInfo`. Pages call `getApp().waitLogin()` before making authenticated requests. +- **API request pattern**: All page-level API requests should be placed inside `app.waitLogin()` callback in `onLoad` to ensure login state is ready before making authenticated requests. Example: `app.waitLogin({ type: 1 }).then(() => { this.fetchData() })`. - **Custom tabbar**: `app.json` has `"custom": true`; the tabbar UI is implemented in components. ## Path Aliases @@ -26,6 +27,7 @@ WeChat Mini Program (微信小程序) — school campus app. Source root is `src - ESLint: `@antfu/eslint-config` + `eslint-config-prettier` (stylistic rules off). Globals `wx`, `App`, `Page`, `Component`, `getApp`, `getCurrentPages`, `requirePlugin`, `requireMiniProgram` are declared. - Prettier: no semicolons, single quotes, trailing commas, printWidth 120. Uses `html` parser for `.wxml`, `css` for `.wxss`, `babel` for `.wxs`. - TypeScript: strict mode (but `noImplicitAny: false`), target ES5, CommonJS modules. Typings in `typings/index.d.ts` declare `IAppOption`, `IAgaxParams`, and extend `WechatMiniprogram.Wx` with `ajax`. +- **Interface usage**: Minimize interface definitions. Only add interfaces when absolutely necessary (e.g., complex API response structures, reusable data models). Prefer inline type annotations or `any` for simple cases. Avoid over-engineering type safety. ## File Structure diff --git a/src/api/request.ts b/src/api/request.ts index 8251f2c..bfc395d 100644 --- a/src/api/request.ts +++ b/src/api/request.ts @@ -25,10 +25,13 @@ export const request = function ( }) } - const app = getApp() + // 获取 accessToken 并构建 Authorization 头 + const accessToken = getApp().globalData.accessToken + const authHeader = accessToken ? { Authorization: `Bearer ${accessToken}` } : {} wx.request({ header: { + ...authHeader, ...header, }, url: gUrl + url, diff --git a/src/app.ts b/src/app.ts index 236f6f4..12f2d3a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -13,7 +13,7 @@ dayjs.extend(relativeTime) App({ globalData: { url: 'https://app.gohighedu.cn', - upFileUrl: 'https://app.gohighedu.cn', + upFileUrl: 'https://app.gohighedu.cn/upload/index', imageUrl: 'https://app.gohighedu.cn/images', Timestamp: new Date().getTime(), @@ -23,6 +23,9 @@ App({ initLoginInfo: {}, userInfo: {}, + + accessToken: '', + needBind: true, }, onLaunch() { this.autoUpdate() @@ -40,40 +43,35 @@ App({ startLogin(callback?: () => void) { wx.login({ success: (res) => { - console.log("DEBUGPRINT[244]: app.ts:42: res=", res) - // // 调用静默登录接口 - // wx.ajax({ - // method: 'POST', - // url: '/auth/silent-login', - // showMsg: false, // 隐藏错误提示 - // data: { - // code: res.code, - // }, - // }) - // .then((response: any) => { - // const { accessToken, user } = response - // - // // 存储 accessToken - // this.globalData.accessToken = accessToken - // - // // 存储用户信息 - // if (user) { - // this.globalData.userInfo = user - // } - // - // // 更新 initLoginInfo - // this.globalData.initLoginInfo = { - // user, - // } - // - // if (callback) { - // callback() - // } - // }) - // .catch((err: any) => { - // // 静默失败,不提示用户 - // console.error('静默登录请求失败:', err) - // }) + // 调用静默登录接口 + wx.ajax({ + method: 'POST', + url: '/auth/silent-login', + showMsg: false, // 隐藏错误提示 + data: { + code: res.code, + }, + }) + .then((response: any) => { + const { accessToken, user, needBind } = response + + // 存储 accessToken + this.globalData.accessToken = accessToken + this.globalData.needBind = needBind + + // 存储用户信息 + if (user) { + this.globalData.userInfo = user + } + + if (callback) { + callback() + } + }) + .catch((err: any) => { + // 静默失败,不提示用户 + console.error('静默登录请求失败:', err) + }) }, fail: (err) => { // 静默失败,不提示用户 @@ -81,52 +79,16 @@ App({ }, }) }, - updateLoginInfo(callback?: () => void) { - wx.ajax({ - method: 'GET', - url: '?r=wtx/user/init-info', - data: {}, - }).then((res: any) => { - this.globalData.initLoginInfo = res - if (callback) { - callback() - } - }) - }, - waitLogin({ type }: { type?: 0 | 1 | 2 | 'any' } = { type: 'any' }) { - return new Promise((resolve, reject) => { + waitLogin({ type }: { type?: 0 | 1 } = { type: 0 }) { + return new Promise((resolve) => { const checkLogin = () => { // type = 0:不需要登录即可访问 if (type === 0) { resolve() return } - - // type = 'any':不检查登录状态 - if (type === 'any') { - resolve() - return - } - - // type = 1 或 2:需要登录 - if (type === 1 || type === 2) { - // 检查是否有 accessToken - if (this.globalData.accessToken) { - // 已登录,检查是否需要绑定 - if (this.globalData.initLoginInfo?.needBind) { - // 需要绑定,跳转到登录页 - this.redirectToLogin(type) - reject(new Error('need_bind')) - return - } - resolve() - return - } - - // 未登录,跳转到登录页 - this.redirectToLogin(type) - reject(new Error('not_logged_in')) - return + if (type === 1 && this.globalData.needBind) { + this.redirectToLogin() } resolve() @@ -138,7 +100,7 @@ App({ /** * 重定向到登录页,并记录当前页面路径 */ - redirectToLogin(_type: 1 | 2) { + redirectToLogin() { // 获取当前页面路径 const pages = getCurrentPages() const currentPage = pages[pages.length - 1] @@ -153,37 +115,6 @@ App({ url: '/pages/login/index', }) }, - - checkLoginType(type: 0 | 1 | 2 | 'any') { - // type = 0:不需要登录 - if (type === 0) { - return true - } - - // type = 'any':不检查 - if (type === 'any') { - return true - } - - // type = 1 或 2:需要登录 - if (type === 1 || type === 2) { - // 检查是否有 accessToken - if (!this.globalData.accessToken) { - this.redirectToLogin(type as 1 | 2) - return false - } - - // 检查是否需要绑定 - if (this.globalData.initLoginInfo?.needBind) { - this.redirectToLogin(type as 1 | 2) - return false - } - - return true - } - - return true - }, getUserInfo(type: 0 | 1 | 2 = 0) { const url: Record = { 0: '?r=wtx/user/userinfo', diff --git a/src/components/pagination/index.js b/src/components/pagination/index.js new file mode 100644 index 0000000..856bf91 --- /dev/null +++ b/src/components/pagination/index.js @@ -0,0 +1,26 @@ +/* global getApp, Component */ +const app = getApp() + +Component({ + externalClasses: ['external-class'], + properties: { + pagination: { + type: Object, + value() { + return {} + }, + }, + customEmpty: { + tyep: Boolean, + value: false, + }, + }, + data: { + imageUrl: app.globalData.imageUrl, + }, + methods: { + handleTouchmove() { + return false + }, + }, +}) diff --git a/src/components/pagination/index.json b/src/components/pagination/index.json new file mode 100644 index 0000000..24029b0 --- /dev/null +++ b/src/components/pagination/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "van-divider": "@vant/weapp/divider/index" + } +} diff --git a/src/components/pagination/index.scss b/src/components/pagination/index.scss new file mode 100644 index 0000000..08a9dea --- /dev/null +++ b/src/components/pagination/index.scss @@ -0,0 +1,6 @@ +/* components/pagination/index.wxss */ +.none { + display: block; + margin: 30rpx auto; + width: 80%; +} diff --git a/src/components/pagination/index.wxml b/src/components/pagination/index.wxml new file mode 100644 index 0000000..1cfc920 --- /dev/null +++ b/src/components/pagination/index.wxml @@ -0,0 +1,9 @@ + + + + + + + 加载中... + +没有更多了 diff --git a/src/components/uploadFile/README.md b/src/components/uploadFile/README.md index 5380ee6..a1b4ab4 100644 --- a/src/components/uploadFile/README.md +++ b/src/components/uploadFile/README.md @@ -8,6 +8,10 @@ 上传接口地址固定为 `app.globalData.upFileUrl`,组件内部自动附加 `loginState`,无需外部传入。 +组件仅提供上传按钮,不维护文件列表状态,不显示文件列表,不包含文件预览功能。父页面需要自行处理文件列表的显示和管理。 + +组件通过事件通知父页面文件选择、上传进度、上传成功、上传失败等状态,父页面需要监听这些事件并更新自己的文件列表。 + ## Properties 入参 | 参数 | 说明 | 类型 | 默认值 | 必填 | @@ -16,9 +20,9 @@ | `maxSize` | 单文件最大限制(byte) | `Number` | `10485760`(10MB) | 否 | | `accept` | 允许的文件类型数组,可选值:`'image'` \| `'video'` \| `'file'` | `Array` | `['image']` | 否 | | `extensions` | 自定义文件后缀(仅 accept 含 `'file'` 时生效),如 `['.pdf', '.doc']` | `Array` | `[]` | 否 | -| `readonly` | 只读模式(不显示上传和删除按钮) | `Boolean` | `false` | 否 | +| `readonly` | 只读模式(不显示上传按钮) | `Boolean` | `false` | 否 | | `useSlot` | 是否使用自定义上传区域插槽 | `Boolean` | `false` | 否 | -| `fileList` | 已有文件列表(用于回显) | `Array` | `[]` | 否 | +| `fileList` | 已有文件列表(用于判断是否还能上传) | `Array` | `[]` | 否 | ### fileList 数据结构 @@ -36,10 +40,8 @@ | 事件名 | 说明 | 回调参数 | | --- | --- | --- | -| `bind:select` | 选中本地文件后触发 | `{ files: UploadFile[] }` | | `bind:success` | 单文件上传完成 | `{ file: UploadFile, response: any }` | | `bind:error` | 上传失败 | `{ file: UploadFile, error: Error }` | -| `bind:remove` | 删除文件 | `{ file: UploadFile, fileList: UploadFile[] }` | ### 事件返回的 UploadFile 结构 @@ -67,13 +69,9 @@ | --- | --- | --- | | `--upload-bg` | 上传区域背景色 | `#f7f8fa` | | `--upload-border` | 边框颜色 | `#e5e7eb` | -| `--upload-text` | 主文字颜色 | `#1f2937` | | `--upload-text-secondary` | 次要文字颜色 | `#9ca3af` | -| `--upload-primary` | 主题色 | `#3b82f6` | -| `--upload-error` | 错误色 | `#ef4444` | -| `--upload-mask` | 遮罩颜色 | `rgba(0, 0, 0, 0.5)` | | `--upload-radius` | 圆角大小 | `16rpx` | -| `--upload-size` | 文件项尺寸(仅默认上传框生效,slot 模式自适应) | `160rpx` | +| `--upload-size` | 上传按钮尺寸(仅默认上传框生效,slot 模式自适应) | `160rpx` | ## 使用案例 @@ -94,27 +92,30 @@ maxCount="{{3}}" maxSize="{{5242880}}" accept="{{['image']}}" - bind:select="onUploadSelect" + fileList="{{fileList}}" bind:success="onUploadSuccess" bind:error="onUploadError" - bind:remove="onUploadRemove" /> ``` ```ts // page.ts Page({ - onUploadSelect(e: WechatMiniprogram.CustomEvent) { - console.log('选中文件', e.detail.files) + data: { + fileList: [] as UploadFile[], }, + onUploadSuccess(e: WechatMiniprogram.CustomEvent) { console.log('上传成功', e.detail.file, e.detail.response) + // 添加上传成功的文件到列表 + this.setData({ + fileList: [...this.data.fileList, e.detail.file], + }) }, + onUploadError(e: WechatMiniprogram.CustomEvent) { console.log('上传失败', e.detail.file, e.detail.error) - }, - onUploadRemove(e: WechatMiniprogram.CustomEvent) { - console.log('删除文件', e.detail.file, e.detail.fileList) + wx.showToast({ title: '文件上传失败', icon: 'none' }) }, }) ``` @@ -127,6 +128,7 @@ Page({ useSlot="{{true}}" maxCount="{{5}}" accept="{{['image', 'video']}}" + fileList="{{fileList}}" bind:success="onUploadSuccess" > diff --git a/src/components/uploadFile/index.scss b/src/components/uploadFile/index.scss index 1efcfa2..81da59f 100644 --- a/src/components/uploadFile/index.scss +++ b/src/components/uploadFile/index.scss @@ -7,218 +7,14 @@ page { --upload-bg: #f7f8fa; --upload-border: #e5e7eb; - --upload-text: #1f2937; --upload-text-secondary: #9ca3af; - --upload-primary: #3b82f6; - --upload-error: #ef4444; - --upload-mask: rgba(0, 0, 0, 0.5); --upload-radius: 16rpx; --upload-size: 160rpx; - --upload-preview-height: 160rpx; // 预览项高度(独立控制) } .upload { width: 100%; - .upload-list { - display: flex; - flex-wrap: wrap; - gap: 16rpx; - } - - .upload-item { - position: relative; - width: 100%; - height: var(--upload-preview-height); - border-radius: var(--upload-radius); - overflow: hidden; - - &--file { - width: 100%; - height: auto; - min-height: var(--upload-preview-height); - } - } - - .upload-preview { - width: 100%; - height: 100%; - position: relative; - - &--file { - display: flex; - align-items: center; - gap: 16rpx; - padding: 20rpx; - background: var(--upload-bg); - border-radius: var(--upload-radius); - } - - .upload-preview-media { - width: 100%; - height: 100%; - display: block; - } - - .upload-preview-play { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - align-items: center; - justify-content: center; - background: var(--upload-mask); - - .upload-preview-play-icon { - width: 0; - height: 0; - border-style: solid; - border-width: 16rpx 0 16rpx 28rpx; - border-color: transparent transparent transparent #fff; - margin-left: 8rpx; - } - } - } - - /* 文件类型预览 */ - .upload-file-icon { - width: 80rpx; - height: 96rpx; - background: #fff; - border: 2rpx solid var(--upload-border); - border-radius: 8rpx; - position: relative; - display: flex; - align-items: flex-end; - justify-content: center; - padding-bottom: 12rpx; - flex-shrink: 0; - - .upload-file-icon-corner { - position: absolute; - top: 0; - right: 0; - width: 24rpx; - height: 24rpx; - background: var(--upload-border); - border-radius: 0 6rpx 0 8rpx; - } - - .upload-file-icon-text { - font-size: 16rpx; - color: var(--upload-text-secondary); - max-width: 60rpx; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } - - .upload-file-name { - flex: 1; - font-size: 26rpx; - color: var(--upload-text); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - /* 上传中遮罩 */ - .upload-mask { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: var(--upload-mask); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 12rpx; - - .upload-progress { - width: 70%; - height: 6rpx; - background: rgba(255, 255, 255, 0.3); - border-radius: 3rpx; - overflow: hidden; - - .upload-progress-bar { - height: 100%; - background: #fff; - border-radius: 3rpx; - transition: width 0.2s ease; - } - } - - .upload-progress-text { - font-size: 24rpx; - color: #fff; - } - - &--error { - .upload-error-text { - font-size: 24rpx; - color: #fff; - } - - .upload-retry { - padding: 8rpx 24rpx; - border: 2rpx solid #fff; - border-radius: 24rpx; - - .upload-retry-text { - font-size: 24rpx; - color: #fff; - } - } - } - } - - /* 删除按钮 */ - .upload-remove { - position: absolute; - top: -8rpx; - right: -8rpx; - width: 36rpx; - height: 36rpx; - background: var(--upload-mask); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - z-index: 2; - - .upload-remove-icon { - position: relative; - width: 18rpx; - height: 18rpx; - - &::before, - &::after { - content: ''; - position: absolute; - top: 50%; - left: 0; - width: 100%; - height: 2rpx; - background: #fff; - transform-origin: center; - } - - &::before { - transform: translateY(-50%) rotate(45deg); - } - - &::after { - transform: translateY(-50%) rotate(-45deg); - } - } - } - /* 上传触发器 */ .upload-trigger { width: var(--upload-size); @@ -226,7 +22,7 @@ page { &--slot { width: 100%; // slot 模式下宽度撑满父容器 - height: auto; // 高度自适应 slot 内容 + height: auto; // slot 模式下高度自适应内容 } .upload-trigger-default { diff --git a/src/components/uploadFile/index.ts b/src/components/uploadFile/index.ts index dbad1d7..301b53a 100644 --- a/src/components/uploadFile/index.ts +++ b/src/components/uploadFile/index.ts @@ -1,6 +1,7 @@ /** * Upload 上传组件 - * 支持图片/视频/文件上传,多文件选择,实时进度条,本地预览,删除附件,失败重试 + * 仅提供上传按钮,不维护文件列表状态 + * 父页面需要自行处理文件列表的显示和管理 */ /** 单个文件对象 */ @@ -57,7 +58,7 @@ Component({ type: Array, value: [], }, - /** 只读模式(不显示上传和删除按钮) */ + /** 只读模式(不显示上传按钮) */ readonly: { type: Boolean, value: false, @@ -67,45 +68,24 @@ Component({ type: Boolean, value: false, }, - /** 已有文件列表(用于回显) */ + /** 已有文件列表(用于判断是否还能上传) */ fileList: { type: Array, value: [], }, }, - data: { - /** 内部文件列表 */ - _fileList: [] as UploadFile[], - }, - - lifetimes: { - attached() { - // 初始化已有文件列表 - if (this.properties.fileList.length > 0) { - this.setData({ - _fileList: this.properties.fileList.map((item: any) => ({ - uid: item.uid || `init_${Date.now()}_${Math.random()}`, - url: item.url, - type: item.type || 'image', - name: item.name || '', - size: item.size || 0, - status: 'success', - progress: 100, - })), - }) - } - }, - }, - methods: { /** * 选择文件 */ onChooseFile() { - if (this.data.readonly) return - const { _fileList, maxCount } = this.data - const remaining = maxCount - _fileList.length + if (this.properties.readonly) return + + const fileList = this.properties.fileList as any[] + const maxCount = this.properties.maxCount + const remaining = maxCount - fileList.length + if (remaining <= 0) { wx.showToast({ title: `最多上传${maxCount}个文件`, icon: 'none' }) return @@ -217,13 +197,6 @@ Component({ if (validFiles.length === 0) return - // 触发 onSelect 事件 - this.triggerEvent('select', { files: validFiles }) - - // 更新文件列表 - const newList = [...this.data._fileList, ...validFiles] - this.setData({ _fileList: newList }) - // 逐个上传 validFiles.forEach((file) => { this.uploadFile(file) @@ -236,128 +209,50 @@ Component({ uploadFile(file: UploadFile) { const app = getApp() const action = app.globalData.upFileUrl - const loginState = app.globalData.loginState + const accessToken = app.globalData.accessToken - this.updateFile(file.uid, { status: 'uploading', progress: 0 }) + const fileType = file.type === 'file' ? 'document' : file.type const task = wx.uploadFile({ url: action, filePath: file.url, name: 'file', - header: { loginState }, - formData: { loginState }, + header: { + Authorization: accessToken ? `Bearer ${accessToken}` : '', + }, + formData: { type: fileType }, success: (res) => { - // 尝试解析返回数据 let data: any try { data = JSON.parse(res.data) } catch { data = res.data } - this.updateFile(file.uid, { - status: 'success', - progress: 100, - url: data.url || data.data?.url || file.url, - }) - this.triggerEvent('success', { file: this.getFile(file.uid), response: data }) + if (data.code === 0 && data.data?.url) { + // 上传成功,更新文件信息 + const successFile = { + ...file, + status: 'success', + progress: 100, + url: data.data.url, + name: data.data.fileName || file.name, + size: data.data.fileSize || file.size, + } + this.triggerEvent('success', { file: successFile, response: data }) + } else { + // 上传失败 + const errorFile = { ...file, status: 'error', progress: 0 } + this.triggerEvent('error', { file: errorFile, error: data.message || '上传失败' }) + wx.showToast({ title: data.message || '上传失败', icon: 'none' }) + } }, fail: (err) => { - this.updateFile(file.uid, { status: 'error', progress: 0 }) - this.triggerEvent('error', { file: this.getFile(file.uid), error: err }) + // 上传失败 + const errorFile = { ...file, status: 'error', progress: 0 } + this.triggerEvent('error', { file: errorFile, error: err }) + wx.showToast({ title: '上传失败', icon: 'none' }) }, }) - - // 监听上传进度 - task.onProgressUpdate((res) => { - this.updateFile(file.uid, { progress: res.progress }) - }) - - // 保存 task 引用 - this.updateFile(file.uid, { _task: task }) - }, - - /** - * 重试上传 - */ - onRetry(e: WechatMiniprogram.BaseEvent) { - const { uid } = e.currentTarget.dataset - const file = this.getFile(uid) - if (file && file.status === 'error') { - this.uploadFile(file) - } - }, - - /** - * 删除文件 - */ - onRemove(e: WechatMiniprogram.BaseEvent) { - const { uid } = e.currentTarget.dataset - const file = this.getFile(uid) - if (!file) return - - // 取消上传任务 - if (file._task) { - file._task.abort() - } - - const newList = this.data._fileList.filter((f) => f.uid !== uid) - this.setData({ _fileList: newList }) - this.triggerEvent('remove', { file, fileList: newList }) - }, - - /** - * 预览图片 - */ - onPreviewImage(e: WechatMiniprogram.BaseEvent) { - const { uid } = e.currentTarget.dataset - const file = this.getFile(uid) - if (!file) return - - const imageList = this.data._fileList.filter((f) => f.type === 'image').map((f) => f.url) - wx.previewImage({ - current: file.url, - urls: imageList, - }) - }, - - /** - * 预览视频 - */ - onPreviewVideo(e: WechatMiniprogram.BaseEvent) { - const { uid } = e.currentTarget.dataset - const file = this.getFile(uid) - if (!file) return - wx.previewMedia({ - sources: [{ url: file.url, type: 'video' }], - current: 0, - }) - }, - - /** - * 更新单个文件状态 - */ - updateFile(uid: string, patch: Partial) { - const list = this.data._fileList.map((f) => { - if (f.uid === uid) { - return { ...f, ...patch } - } - return f - }) - this.setData({ _fileList: list }) - }, - - /** - * 获取单个文件 - */ - getFile(uid: string): UploadFile | undefined { - return this.data._fileList.find((f) => f.uid === uid) - }, - - /** - * 获取当前文件列表(供外部调用) - */ - getFileList(): UploadFile[] { - return this.data._fileList }, }, }) diff --git a/src/components/uploadFile/index.wxml b/src/components/uploadFile/index.wxml index f06131f..19764b5 100644 --- a/src/components/uploadFile/index.wxml +++ b/src/components/uploadFile/index.wxml @@ -1,92 +1,20 @@ - - - - - - - - - - - - - - - - - - {{item.name}} - - {{item.name}} - - - - - - - - {{item.progress}}% - - - - - 上传失败 - - 重试 - - - - - - - - - - - - - - - - - + + + + + + + diff --git a/src/images/none.png b/src/images/none.png new file mode 100644 index 0000000000000000000000000000000000000000..e0c650f58c650901813b1ef5315f39cc9f874947 GIT binary patch literal 78543 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 V_9p&6?l}Me002ovPDHLkV1hb7uZaKv literal 0 HcmV?d00001 diff --git a/src/pages/act/index.json b/src/pages/act/index.json index e33ae3f..5e44518 100644 --- a/src/pages/act/index.json +++ b/src/pages/act/index.json @@ -2,6 +2,7 @@ "navigationStyle": "default", "navigationBarTitleText": "活动", "usingComponents": { - "van-popup": "@vant/weapp/popup/index" + "van-popup": "@vant/weapp/popup/index", + "pagination": "/components/pagination/index" } } diff --git a/src/pages/act/index.scss b/src/pages/act/index.scss index 770d218..5979e9c 100644 --- a/src/pages/act/index.scss +++ b/src/pages/act/index.scss @@ -7,7 +7,7 @@ page { padding: 30rpx 30rpx 0; background-color: #fff; .search { - margin: 30rpx; + margin: 30rpx 0; padding: 12rpx 24rpx; display: flex; align-items: center; @@ -27,6 +27,7 @@ page { .options { display: flex; .tabs { + margin: 0 -16rpx; flex: 1; overflow-x: auto; display: flex; @@ -38,7 +39,7 @@ page { } .tab { flex-shrink: 0; - padding: 10rpx 18rpx 16rpx; + padding: 10rpx 32rpx 16rpx; font-size: 28rpx; color: rgba(71, 85, 105, 1); &.active { @@ -128,12 +129,13 @@ page { } } .banner { - margin: 30rpx 30rpx 0; + margin: 0 30rpx 0; height: 230rpx; .b-img { display: block; width: 100%; height: 100%; + border-radius: 24rpx; } } .list { @@ -207,9 +209,10 @@ page { font-size: 28rpx; color: rgba(100, 116, 139, 1); display: flex; - align-items: center; gap: 8rpx; .icon { + margin-top: 8rpx; + flex-shrink: 0; width: 22rpx; height: 22rpx; } @@ -271,6 +274,12 @@ page { padding: 15rpx 30rpx; background: #f7f8fa; border-radius: 15rpx 15rpx 15rpx 15rpx; + border: 1px solid #f7f8fa; + &.active{ + background-color: rgba(157, 223, 253, 0.16); + border: 1px solid rgba(74, 184, 253, 0.32); + color: rgba(74, 184, 253, 1); + } } } .date-raneg { diff --git a/src/pages/act/index.ts b/src/pages/act/index.ts index d1531fc..49c4720 100644 --- a/src/pages/act/index.ts +++ b/src/pages/act/index.ts @@ -1,52 +1,489 @@ -const _app = getApp() +const app = getApp() + +interface ILevelItem { + id: number + name: string + code: string + sort: number +} + +interface ICategoryItem { + id: number + name: string + code: string + icon: string + sort: number + isEnabled: number +} + +interface IActivityItem { + id: number + name: string + type: number + typeOther: string + mainImages: string[] + summary: string + description: string + regType: number + regCondition: string + contactName: string + contactPhone: string + startAt: string + endAt: string + location: string + status: string +} + +interface IPagination { + page: number + pageSize: number + pages: number + count: number +} Page({ data: { - filterShow: true, + filterShow: false, + // 活动等级列表 + levelList: [] as ILevelItem[], + // 等级 Tab 列表(包含"全部等级") + levelTabs: [ + { + id: 0, + name: '全部', + }, + ] as Array<{ id: number; name: string }>, + // 当前选中的等级索引 + currentLevelIndex: 0, + // 活动分类列表 + categoryList: [] as ICategoryItem[], + // 分类 Tab 列表(包含"全部分类") typeList: [ { + id: 0, name: '全部分类', - icon: '5', - iconActive: '6', + icon: '/images/icon5.png', + iconActive: '/images/icon6.png', + isSelected: true, }, - { - name: '学术科技', - icon: '7', - iconActive: '8', + ] as Array<{ id: number; name: string; icon: string; iconActive: string; isSelected: boolean }>, + // 当前选中的分类 ID 数组(支持多选) + selectedCategoryIds: [] as number[], + // 活动列表 + activityList: [] as IActivityItem[], + // 分页信息 + pagination: { + page: 1, + pageSize: 20, + pages: 0, + count: 0, + } as IPagination, + // 加载状态 + loading: false, + // 筛选参数 + filters: { + status: '', + keyword: '', + levelId: 0, + categoryIds: [] as number[], + startTime: '', + endTime: '', + }, + // 时间快捷选项 + timeOptions: [ + { id: 0, name: '全部时间' }, + { id: 1, name: '今天' }, + { id: 2, name: '本周' }, + { id: 3, name: '本月' }, + { id: 4, name: '自定义时间' }, + ], + // 当前选中的时间选项索引 + selectedTimeIndex: 0, + // 自定义时间范围(用于 picker 显示) + customStartTime: '', + customEndTime: '', + }, + + onLoad() { + // 在 waitLogin 回调中请求接口 + app.waitLogin({ type: 1 }).then(() => { + this.fetchLevelList() + this.fetchCategoryList() + this.fetchActivityList() + }) + }, + + // 获取活动等级列表 + async fetchLevelList() { + try { + const res = await wx.ajax({ + url: '/activity-level/list', + method: 'GET', + data: {}, + }) + if (res && res.list) { + // 构建 levelTabs,在开头添加"全部" + const levelTabs = [ + { + id: 0, + name: '全部', + }, + ...res.list.map((item: ILevelItem) => ({ + id: item.id, + name: item.name, + })), + ] + this.setData({ + levelList: res.list, + levelTabs, + }) + } + } catch (err) { + console.error('获取活动等级列表失败:', err) + } + }, + + // 获取活动分类列表 + async fetchCategoryList() { + try { + const res = await wx.ajax({ + url: '/activity-category/list', + method: 'GET', + data: {}, + }) + if (res && res.list) { + const { selectedCategoryIds } = this.data + // 构建 typeList,在开头添加"全部分类",并计算选中状态 + const typeList = [ + { + id: 0, + name: '全部分类', + icon: '/images/icon5.png', + iconActive: '/images/icon6.png', + isSelected: selectedCategoryIds.length === 0, + }, + ...res.list.map((item: ICategoryItem) => ({ + id: item.id, + name: item.name, + icon: item.icon || '/images/icon5.png', + iconActive: item.icon || '/images/icon6.png', + isSelected: selectedCategoryIds.includes(item.id), + })), + ] + this.setData({ + categoryList: res.list, + typeList, + }) + } + } catch (err) { + console.error('获取活动分类列表失败:', err) + } + }, + + // 获取活动列表 + async fetchActivityList(isRefresh = false) { + if (this.data.loading) return + + const { pagination, filters, selectedCategoryIds, currentLevelIndex, levelTabs } = this.data + const page = isRefresh ? 1 : pagination.page + + this.setData({ loading: true }) + + try { + // 构建请求参数 + const params: Record = { + page, + pageSize: pagination.pageSize, + } + + // 添加筛选参数 + if (filters.status) params.status = filters.status + if (filters.keyword) params.keyword = filters.keyword + if (filters.startTime) params.startTime = filters.startTime + if (filters.endTime) params.endTime = filters.endTime + + // 等级筛选:当前选中的等级(非"全部等级") + if (currentLevelIndex > 0 && levelTabs[currentLevelIndex]) { + params.levelId = levelTabs[currentLevelIndex].id + } + + // 分类筛选:选中的分类 ID 数组(非空时传值) + if (selectedCategoryIds.length > 0) { + params.categoryIds = selectedCategoryIds + } + + const res = await wx.ajax({ + url: '/activity/list', + method: 'GET', + data: params, + }) + + if (res) { + const newList = isRefresh ? res.list : [...this.data.activityList, ...res.list] + this.setData({ + activityList: newList, + pagination: { + page: res.page || page, + pageSize: res.pageSize || pagination.pageSize, + pages: res.pages || 0, + count: res.count || 0, + }, + }) + } + } catch (err) { + console.error('获取活动列表失败:', err) + } finally { + this.setData({ loading: false }) + } + }, + + // 切换分类(支持多选) + handleTypeChange(e: WechatMiniprogram.TouchEvent) { + const id = e.currentTarget.dataset.id + let { selectedCategoryIds, typeList } = this.data + + // 点击"全部分类"(id=0)时,清空所有选中 + if (id === 0) { + selectedCategoryIds = [] + } else { + // 点击其他分类时 + const index = selectedCategoryIds.indexOf(id) + if (index > -1) { + // 已选中,取消选中 + selectedCategoryIds = selectedCategoryIds.filter((item) => item !== id) + } else { + // 未选中,添加选中 + selectedCategoryIds = [...selectedCategoryIds, id] + } + } + + // 更新 typeList 的选中状态 + typeList = typeList.map((item) => ({ + ...item, + isSelected: item.id === 0 ? selectedCategoryIds.length === 0 : selectedCategoryIds.includes(item.id), + })) + + this.setData({ + selectedCategoryIds, + typeList, + activityList: [], + pagination: { + page: 1, + pageSize: 20, + pages: 0, + count: 0, }, - { - name: '文体艺术', - icon: '9', - iconActive: '10', + }) + this.fetchActivityList(true) + }, + + // 切换等级 Tab + handleLevelChange(e: WechatMiniprogram.TouchEvent) { + const index = e.currentTarget.dataset.index + if (index === this.data.currentLevelIndex) return + + this.setData({ + currentLevelIndex: index, + activityList: [], + pagination: { + page: 1, + pageSize: 20, + pages: 0, + count: 0, }, - { - name: '志愿公益', - icon: '11', - iconActive: '12', + }) + this.fetchActivityList(true) + }, + + // 下拉刷新 + onPullDownRefresh() { + this.setData({ + activityList: [], + pagination: { + page: 1, + pageSize: 20, + pages: 0, + count: 0, }, - { - name: '创新创业', - icon: '13', - iconActive: '14', + }) + this.fetchActivityList(true).then(() => { + wx.stopPullDownRefresh() + }) + }, + + // 上拉加载更多 + onReachBottom() { + const { pagination, loading } = this.data + if (loading || pagination.page >= pagination.pages) return + + this.setData({ + pagination: { + ...pagination, + page: pagination.page + 1, }, - ], + }) + this.fetchActivityList() }, - onLoad() {}, + + // 关闭筛选弹窗 handlePopupClose() { this.setData({ filterShow: false, }) }, + + // 打开筛选弹窗 + handleFilterOpen() { + this.setData({ + filterShow: true, + }) + }, + + // 选择时间快捷选项 + handleTimeOptionChange(e: WechatMiniprogram.TouchEvent) { + const index = e.currentTarget.dataset.index + this.setData({ + selectedTimeIndex: index, + }) + + // 根据选项设置时间范围 + const today = new Date() + const formatDate = (date: Date) => { + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` + } + + let startTime = '' + let endTime = '' + + switch (index) { + case 0: // 全部时间 + startTime = '' + endTime = '' + break + case 1: // 今天 + startTime = formatDate(today) + endTime = formatDate(today) + break + case 2: { // 本周 + const weekStart = new Date(today) + weekStart.setDate(today.getDate() - today.getDay() + 1) + const weekEnd = new Date(weekStart) + weekEnd.setDate(weekStart.getDate() + 6) + startTime = formatDate(weekStart) + endTime = formatDate(weekEnd) + break + } + case 3: { // 本月 + const monthStart = new Date(today.getFullYear(), today.getMonth(), 1) + const monthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0) + startTime = formatDate(monthStart) + endTime = formatDate(monthEnd) + break + } + case 4: // 自定义时间 + // 使用已选择的自定义时间,或清空 + startTime = this.data.customStartTime + endTime = this.data.customEndTime + break + } + + this.setData({ + filters: { + ...this.data.filters, + startTime, + endTime, + }, + }) + }, + + // 选择开始时间 + handleStartTimeChange(e: WechatMiniprogram.PickerChange) { + const date = e.detail.value as string + this.setData({ + customStartTime: date, + selectedTimeIndex: 4, // 自动切换到自定义时间 + filters: { + ...this.data.filters, + startTime: date, + }, + }) + }, + + // 选择结束时间 + handleEndTimeChange(e: WechatMiniprogram.PickerChange) { + const date = e.detail.value as string + this.setData({ + customEndTime: date, + selectedTimeIndex: 4, // 自动切换到自定义时间 + filters: { + ...this.data.filters, + endTime: date, + }, + }) + }, + + // 取消筛选 + handleFilterCancel() { + // 重置筛选条件 + this.setData({ + filterShow: false, + selectedTimeIndex: 0, + customStartTime: '', + customEndTime: '', + filters: { + status: '', + keyword: '', + levelId: 0, + categoryIds: [], + startTime: '', + endTime: '', + }, + }) + this.fetchActivityList(true) + }, + + // 确定筛选 + handleFilterConfirm() { + this.setData({ + filterShow: false, + activityList: [], + pagination: { + page: 1, + pageSize: 20, + pages: 0, + count: 0, + }, + }) + this.fetchActivityList(true) + }, + + // 申请活动 handleApply() { wx.navigateTo({ url: '/pages/actAdd/index', }) }, - handleDetail() { + + // 查看活动详情 + handleDetail(e: WechatMiniprogram.TouchEvent) { + const id = e.currentTarget.dataset.id wx.navigateTo({ - url: '/pages/actDetail/index', + url: `/pages/actDetail/index?id=${id}`, }) }, + + // 获取活动状态文本 + getStatusText(status: string): string { + const statusMap: Record = { + registering: '报名中', + running: '进行中', + ended: '已结束', + } + return statusMap[status] || status + }, }) export {} diff --git a/src/pages/act/index.wxml b/src/pages/act/index.wxml index 0ee4220..039fc3b 100644 --- a/src/pages/act/index.wxml +++ b/src/pages/act/index.wxml @@ -6,89 +6,122 @@ - 全部 + + {{item.name}} + - + 筛选 + + - - - - 全部分类 + + + + {{item.name}} + + + + - + - 进行中 - - 128人已报名 + {{item.activityStatusName}} + + {{item.regCount}}人已报名 - - 深职大第十五届校园歌手大赛深职大第十五届校园歌手大深职大第十五届校园歌手大深职大第十五届校园歌手大赛赛赛 - + {{item.name}} - 2026.04.01-2026.05.30 + {{item.startAt}} - {{item.endAt}} - 留仙洞校区音乐厅 + {{item.location}} + + + + 活动申请 + 活动开始时间 - 全部时间 - 今天 - 本周 - 本月 - 自定义时间 + + {{item.name}} + - + 时间范围 - + - 2026-05-28 + {{customStartTime || '请选择'}} - - + - 2026-05-28 + {{customEndTime || '请选择'}} - 取消 - 确定 + 取消 + 确定 diff --git a/src/pages/actAdd/index.json b/src/pages/actAdd/index.json index c72cee9..ef5b8d3 100644 --- a/src/pages/actAdd/index.json +++ b/src/pages/actAdd/index.json @@ -2,6 +2,7 @@ "navigationBarTitleText": "创建活动", "navigationStyle": "default", "usingComponents": { - "upload-file": "/components/uploadFile/index" + "upload-file": "/components/uploadFile/index", + "van-icon": "@vant/weapp/icon/index" } } diff --git a/src/pages/actAdd/index.scss b/src/pages/actAdd/index.scss index c81123c..da71571 100644 --- a/src/pages/actAdd/index.scss +++ b/src/pages/actAdd/index.scss @@ -15,6 +15,13 @@ page { background: #f5f7fa; } +/* ========== 自定义预览样式 ========== */ +.upload-preview-image { + width: 100%; + height: 100%; + display: block; +} + /* ========== 步骤条 ========== */ .step-bar { flex-shrink: 0; @@ -284,6 +291,29 @@ page { } } +.upload-preview { + position: relative; + width: 100%; + height: 350rpx; + border-radius: 16rpx; + .p-img { + display: block; + width: 100%; + height: 100%; + border-radius: 16rpx; + } + .close { + padding: 10rpx; + position: absolute; + top: 0; + right: 0; + background-color: rgba(0, 0, 0, 0.6); + color: #fff; + font-size: 20rpx; + border-radius: 0 16rpx 0 16rpx; + } +} + /* ========== 标签选择 ========== */ .tag-list { display: grid; diff --git a/src/pages/actAdd/index.ts b/src/pages/actAdd/index.ts index 08e3dad..a37997a 100644 --- a/src/pages/actAdd/index.ts +++ b/src/pages/actAdd/index.ts @@ -1,10 +1,36 @@ const app = getApp() -interface AgendaItem { +const DRAFT_KEY = 'actAdd:draft' + +interface ILevelItem { + id: number + name: string + code: string + sort: number +} + +interface ICategoryItem { + id: number + name: string + code: string + icon: string + sort: number + isEnabled: number +} + +interface ITagItem { id: number - time: string + name: string + sort: number + activityCount: number +} + +interface AgendaItem { + agendaDate: string + agendaTime: string title: string - desc: string + description: string + sort: number } Page({ @@ -17,25 +43,43 @@ Page({ ], // 步骤1 基本信息 - coverImageList: [] as Array<{ uid: string, url: string, type: string, name: string, size: number }>, + coverImageList: [] as Array<{ uid: string; url: string; type: string; name: string; size: number }>, title: '', + type: 1, + typeOptions: [ + { id: 1, name: '讲座' }, + { id: 2, name: '比赛' }, + { id: 3, name: '社团' }, + { id: 4, name: '志愿' }, + { id: 5, name: '文体' }, + { id: 6, name: '其他' }, + ], + typeOther: '', + summary: '', startTime: '', endTime: '', detail: '', - level: '', - levels: ['校级', '院级', '系级', '班级', '其他'], - category: '', - categories: ['讲座', '比赛', '社团活动', '志愿服务', '文体活动', '学术交流', '其他'], + detailImages: [] as string[], location: '', organizer: '', contactName: '', contactPhone: '', - // 步骤2 活动议程 - agendas: [{ id: 1, time: '', title: '', desc: '' }] as AgendaItem[], - nextAgendaId: 2, + // 活动等级 + levelList: [] as ILevelItem[], + levelId: 0, + + // 活动分类 + categoryList: [] as ICategoryItem[], + selectedCategoryIds: [] as number[], + categoryTags: [] as Array<{ id: number; name: string; isSelected: boolean }>, // 用于显示选中状态 + + // 活动标签 + tagList: [] as ITagItem[], + selectedTagIds: [] as number[], + tagTags: [] as Array<{ id: number; name: string; isSelected: boolean }>, // 用于显示选中状态 - // 步骤3 报名签到设置 + // 步骤2 报名签到设置 needRegister: true, registerStartTime: '', registerEndTime: '', @@ -46,120 +90,446 @@ Page({ checkinWay: 'dynamic', checkinStartTime: '', checkinEndTime: '', + + // 步骤3 活动议程 + agendas: [{ agendaDate: '', agendaTime: '', title: '', description: '', sort: 0 }] as AgendaItem[], + nextAgendaId: 2, + + // 提交状态 + submitting: false, }, - onLoad() { - app.waitLogin() + async onLoad() { + await app.waitLogin({ type: 1 }) + try { + await Promise.all([this.fetchLevelList(), this.fetchCategoryList(), this.fetchTagList()]) + } catch (err) { + console.error('初始化数据失败:', err) + } + + // 检查是否有本地草稿 + try { + const draft = wx.getStorageSync(DRAFT_KEY) as any + if (draft && Object.keys(draft).length) { + wx.showModal({ + title: '检测到未完成的活动', + content: '上次未完成的表单已保存,是否继续编辑?', + confirmText: '继续编辑', + cancelText: '重新开始', + success: (res) => { + if (res.confirm) { + this.restoreDraft(draft) + } else { + // 用户选择重新开始,清除草稿 + this.clearDraft() + } + }, + }) + } + } catch (err) { + console.error('读取本地草稿失败:', err) + } + }, + + // ========== 草稿管理 ========== + buildDraft(partial: Record = {}) { + const keys = [ + 'currentStep', + 'coverImageList', + 'title', + 'type', + 'typeOther', + 'summary', + 'startTime', + 'endTime', + 'detail', + 'detailImages', + 'location', + 'organizer', + 'contactName', + 'contactPhone', + 'levelId', + 'selectedCategoryIds', + 'selectedTagIds', + 'needRegister', + 'registerStartTime', + 'registerEndTime', + 'registerLimit', + 'registerLimitCount', + 'registerCondition', + 'checkinWay', + 'checkinStartTime', + 'checkinEndTime', + 'agendas', + ] + + const draft: Record = {} + keys.forEach((k) => { + if (Object.prototype.hasOwnProperty.call(partial, k)) { + draft[k] = partial[k] + } else { + draft[k] = (this.data as any)[k] + } + }) + return draft + }, + + saveDraft(partial: Record = {}) { + try { + const draft = this.buildDraft(partial) + wx.setStorageSync(DRAFT_KEY, draft) + } catch (err) { + console.error('保存草稿失败:', err) + } + }, + + clearDraft() { + try { + wx.removeStorageSync(DRAFT_KEY) + } catch { + /* ignore */ + } + }, + + restoreDraft(draft: Record) { + if (!draft) return + const safeDraft = { ...draft } + safeDraft.coverImageList = safeDraft.coverImageList || [] + safeDraft.detailImages = safeDraft.detailImages || [] + safeDraft.agendas = safeDraft.agendas || [] + safeDraft.selectedCategoryIds = safeDraft.selectedCategoryIds || [] + safeDraft.selectedTagIds = safeDraft.selectedTagIds || [] + + this.setData(safeDraft) + + // 重建标签选中状态显示 + const categoryTags = (this.data.categoryList || []).map((item: ICategoryItem) => ({ + id: item.id, + name: item.name, + isSelected: safeDraft.selectedCategoryIds.includes(item.id), + })) + const tagTags = (this.data.tagList || []).map((item: ITagItem) => ({ + id: item.id, + name: item.name, + isSelected: safeDraft.selectedTagIds.includes(item.id), + })) + this.setData({ categoryTags, tagTags }) + }, + + setAndSave(patch: Record) { + this.setData(patch) + this.saveDraft(patch) + }, + + // 获取活动等级列表 + async fetchLevelList() { + try { + const res = await wx.ajax({ + url: '/activity-level/list', + method: 'GET', + data: {}, + }) + if (res && res.list) { + this.setData({ levelList: res.list }) + } + } catch (err) { + console.error('获取活动等级列表失败:', err) + } + }, + + // 获取活动分类列表 + async fetchCategoryList() { + try { + const res = await wx.ajax({ + url: '/activity-category/list', + method: 'GET', + data: {}, + }) + if (res && res.list) { + const { selectedCategoryIds } = this.data + const categoryTags = res.list.map((item: ICategoryItem) => ({ + id: item.id, + name: item.name, + isSelected: selectedCategoryIds.includes(item.id), + })) + this.setData({ + categoryList: res.list, + categoryTags, + }) + } + } catch (err) { + console.error('获取活动分类列表失败:', err) + } + }, + + // 获取活动标签列表 + async fetchTagList() { + try { + const res = await wx.ajax({ + url: '/activity-tag/list', + method: 'GET', + data: {}, + }) + if (res && res.list) { + const { selectedTagIds } = this.data + const tagTags = res.list.map((item: ITagItem) => ({ + id: item.id, + name: item.name, + isSelected: selectedTagIds.includes(item.id), + })) + this.setData({ + tagList: res.list, + tagTags, + }) + } + } catch (err) { + console.error('获取活动标签列表失败:', err) + } }, // ========== 步骤切换 ========== goStep(step: number) { if (step < 1 || step > 4) return - this.setData({ currentStep: step }) + this.setAndSave({ currentStep: step }) + }, + + // 验证当前步骤的必填项 + validateCurrentStep(): boolean { + const { + currentStep, + coverImageList, + title, + startTime, + endTime, + location, + needRegister, + registerStartTime, + registerEndTime, + checkinWay, + checkinStartTime, + checkinEndTime, + agendas, + } = this.data + + // 步骤1:基本信息 + if (currentStep === 1) { + if (!coverImageList.length) { + wx.showToast({ title: '请上传活动头图', icon: 'none' }) + return false + } + if (!title.trim()) { + wx.showToast({ title: '请输入活动标题', icon: 'none' }) + return false + } + if (!startTime) { + wx.showToast({ title: '请选择活动开始时间', icon: 'none' }) + return false + } + if (!endTime) { + wx.showToast({ title: '请选择活动结束时间', icon: 'none' }) + return false + } + if (!location.trim()) { + wx.showToast({ title: '请输入活动地点', icon: 'none' }) + return false + } + } + + // 步骤2:报名签到设置 + if (currentStep === 2) { + if (needRegister) { + if (!registerStartTime) { + wx.showToast({ title: '请选择报名开始时间', icon: 'none' }) + return false + } + if (!registerEndTime) { + wx.showToast({ title: '请选择报名截止时间', icon: 'none' }) + return false + } + } + + // 签到时间校验(动态二维码或固定二维码时必填) + if (checkinWay !== 'none') { + if (!checkinStartTime) { + wx.showToast({ title: '请选择签到开始时间', icon: 'none' }) + return false + } + if (!checkinEndTime) { + wx.showToast({ title: '请选择签到结束时间', icon: 'none' }) + return false + } + } + } + + // 步骤3:活动议程 + if (currentStep === 3) { + if (!agendas.length || !agendas[0].title.trim()) { + wx.showToast({ title: '请添加活动议程', icon: 'none' }) + return false + } + } + + return true }, onNextStep() { + // 先验证当前步骤 + if (!this.validateCurrentStep()) { + return + } + const next = this.data.currentStep + 1 - if (next <= 4) this.setData({ currentStep: next }) + if (next <= 4) this.setAndSave({ currentStep: next }) }, onPrevStep() { const prev = this.data.currentStep - 1 - if (prev >= 1) this.setData({ currentStep: prev }) + if (prev >= 1) this.setAndSave({ currentStep: prev }) }, // ========== 图片上传 ========== - onCoverUploadSuccess(e: WechatMiniprogram.CustomEvent) { + // 上传成功后,直接添加到列表(maxCount=1,只保留一个) + onCoverSuccess(e: WechatMiniprogram.CustomEvent) { const { file } = e.detail - this.setData({ coverImageList: [file] }) + this.setAndSave({ coverImageList: [file] }) + }, + + // 上传失败后,显示错误 + onCoverError(_e: WechatMiniprogram.CustomEvent) { + wx.showToast({ title: '上传失败,请重试', icon: 'none' }) + }, + + // 删除封面图片 + handleDelCover() { + this.setAndSave({ coverImageList: [] }) }, - onCoverRemove(_e: WechatMiniprogram.CustomEvent) { - this.setData({ coverImageList: [] }) + onDetailImageSuccess(e: WechatMiniprogram.CustomEvent) { + const { urls } = e.detail + this.setAndSave({ detailImages: urls }) }, // ========== 输入绑定 ========== onInputChange(e: WechatMiniprogram.Input) { const { field } = e.currentTarget.dataset - this.setData({ [field]: e.detail.value }) + this.setAndSave({ [field]: e.detail.value }) }, onTextareaChange(e: WechatMiniprogram.TextareaInput) { const { field } = e.currentTarget.dataset - this.setData({ [field]: e.detail.value }) + this.setAndSave({ [field]: e.detail.value }) }, // ========== 时间选择 ========== onPickTime(e: WechatMiniprogram.PickerChange) { const { field } = e.currentTarget.dataset - this.setData({ [field]: e.detail.value }) + this.setAndSave({ [field]: e.detail.value }) }, - // ========== 标签选择 ========== - onSelectLevel(e: WechatMiniprogram.TouchEvent) { + // ========== 活动类型选择 ========== + onSelectType(e: WechatMiniprogram.TouchEvent) { const { value } = e.currentTarget.dataset - this.setData({ level: value }) + this.setAndSave({ type: value }) + }, + + // ========== 活动等级选择 ========== + onSelectLevel(e: WechatMiniprogram.TouchEvent) { + const { id } = e.currentTarget.dataset + this.setAndSave({ levelId: id }) }, + // ========== 活动分类选择(多选) ========== onSelectCategory(e: WechatMiniprogram.TouchEvent) { - const { value } = e.currentTarget.dataset - this.setData({ category: value }) + const { id } = e.currentTarget.dataset + let { selectedCategoryIds, categoryTags } = this.data + + const index = selectedCategoryIds.indexOf(id) + if (index > -1) { + selectedCategoryIds = selectedCategoryIds.filter((item) => item !== id) + } else { + selectedCategoryIds = [...selectedCategoryIds, id] + } + + categoryTags = categoryTags.map((item) => ({ + ...item, + isSelected: selectedCategoryIds.includes(item.id), + })) + + this.setAndSave({ selectedCategoryIds, categoryTags }) + }, + + // ========== 活动标签选择(多选) ========== + onSelectTag(e: WechatMiniprogram.TouchEvent) { + const { id } = e.currentTarget.dataset + let { selectedTagIds, tagTags } = this.data + + const index = selectedTagIds.indexOf(id) + if (index > -1) { + selectedTagIds = selectedTagIds.filter((item) => item !== id) + } else { + selectedTagIds = [...selectedTagIds, id] + } + + tagTags = tagTags.map((item) => ({ + ...item, + isSelected: selectedTagIds.includes(item.id), + })) + + this.setAndSave({ selectedTagIds, tagTags }) }, // ========== 议程管理 ========== onAddAgenda() { const agendas = this.data.agendas agendas.push({ - id: this.data.nextAgendaId, - time: '', + agendaDate: '', + agendaTime: '', title: '', - desc: '', - }) - this.setData({ - agendas, - nextAgendaId: this.data.nextAgendaId + 1, + description: '', + sort: agendas.length, }) + this.setAndSave({ agendas }) }, onRemoveAgenda(e: WechatMiniprogram.TouchEvent) { const { index } = e.currentTarget.dataset const agendas = this.data.agendas.filter((_, i) => i !== index) - this.setData({ agendas }) + this.setAndSave({ agendas }) }, onAgendaInput(e: WechatMiniprogram.Input | WechatMiniprogram.TextareaInput) { const { index, field } = e.currentTarget.dataset const agendas = this.data.agendas agendas[index][field] = e.detail.value - this.setData({ agendas }) + this.setAndSave({ agendas }) }, onAgendaTime(e: WechatMiniprogram.PickerChange) { - const { index } = e.currentTarget.dataset + const { index, field } = e.currentTarget.dataset const agendas = this.data.agendas - agendas[index].time = e.detail.value as string - this.setData({ agendas }) + agendas[index][field] = e.detail.value as string + this.setAndSave({ agendas }) }, // ========== 报名签到设置 ========== onToggleRegister(e: WechatMiniprogram.TouchEvent) { const { value } = e.currentTarget.dataset - this.setData({ needRegister: value === 'yes' }) + this.setAndSave({ needRegister: value === 'yes' }) }, onToggleRegisterLimit(e: WechatMiniprogram.TouchEvent) { const { value } = e.currentTarget.dataset - this.setData({ registerLimit: value }) + this.setAndSave({ registerLimit: value }) }, onSelectCheckinWay(e: WechatMiniprogram.TouchEvent) { const { value } = e.currentTarget.dataset - this.setData({ checkinWay: value }) + this.setAndSave({ checkinWay: value }) }, // ========== 底部操作 ========== onSaveDraft() { - wx.showToast({ title: '已保存草稿', icon: 'success' }) + this.submitActivity(1) // activityStatus = 1 (草稿) }, onSubmit() { @@ -168,13 +538,169 @@ Page({ content: '提交后将进入审核流程,是否继续?', success: (res) => { if (res.confirm) { - this.setData({ currentStep: 4 }) + this.submitActivity(2) // activityStatus = 2 (待审核) } }, }) }, + // 提交活动申请 + async submitActivity(activityStatus: number) { + const { + coverImageList, + title, + type, + typeOther, + summary, + startTime, + endTime, + location, + organizer, + contactName, + contactPhone, + levelId, + selectedCategoryIds, + selectedTagIds, + needRegister, + registerStartTime, + registerEndTime, + registerLimit, + registerLimitCount, + registerCondition, + checkinWay, + checkinStartTime, + checkinEndTime, + agendas, + detailImages, + submitting, + } = this.data + + if (submitting) return + + // 校验必填字段 + if (!coverImageList.length) { + wx.showToast({ title: '请上传活动头图', icon: 'error' }) + return + } + if (!title.trim()) { + wx.showToast({ title: '请输入活动标题', icon: 'error' }) + return + } + if (!startTime) { + wx.showToast({ title: '请选择活动开始时间', icon: 'error' }) + return + } + if (!endTime) { + wx.showToast({ title: '请选择活动结束时间', icon: 'error' }) + return + } + if (!location.trim()) { + wx.showToast({ title: '请输入活动地点', icon: 'error' }) + return + } + + // 校验报名设置 + if (needRegister) { + if (!registerStartTime) { + wx.showToast({ title: '请选择报名开始时间', icon: 'error' }) + return + } + if (!registerEndTime) { + wx.showToast({ title: '请选择报名截止时间', icon: 'error' }) + return + } + } + + // 校验签到设置 + if (checkinWay !== 'none') { + if (!checkinStartTime) { + wx.showToast({ title: '请选择签到开始时间', icon: 'error' }) + return + } + if (!checkinEndTime) { + wx.showToast({ title: '请选择签到结束时间', icon: 'error' }) + return + } + } + + // 校验议程 + if (!agendas.length || !agendas[0].title.trim()) { + wx.showToast({ title: '请添加活动议程', icon: 'error' }) + return + } + + this.setData({ submitting: true }) + wx.showLoading({ title: activityStatus === 1 ? '保存中...' : '提交中...' }) + + try { + const checkinTypeMap: Record = { + dynamic: 1, + fixed: 2, + none: 3, + } + + const params: Record = { + mainImages: coverImageList.map((item) => item.url), + name: title, + type, + typeOther: type === 6 ? typeOther : '', + summary, + detailImages, + checkinType: checkinTypeMap[checkinWay], + regType: needRegister ? 1 : 2, + regCondition: registerCondition, + quota: registerLimit === 'limited' ? Number(registerLimitCount) : 0, + regStartAt: needRegister ? registerStartTime : '', + regEndAt: needRegister ? registerEndTime : '', + startAt: startTime, + endAt: endTime, + location, + organizer, + contactName, + contactPhone, + tagIds: selectedTagIds, + categoryIds: selectedCategoryIds, + levelId, + agendas: agendas.map((item, index) => ({ + ...item, + sort: index, + })), + activityStatus, + } + + const res = await wx.ajax({ + url: '/activity/apply', + method: 'POST', + data: params, + }) + + wx.hideLoading() + + if (res) { + wx.showToast({ + title: activityStatus === 1 ? '已保存草稿' : '提交成功', + icon: 'success', + }) + + // 清理本地草稿 + this.clearDraft() + + // 跳转到结果页面 + wx.redirectTo({ + url: `/pages/actAddResult/index?id=${res.activityId}&status=${res.status}`, + }) + } + } catch (err: any) { + wx.hideLoading() + const message = err?.message || '提交失败' + wx.showToast({ title: message, icon: 'error' }) + } finally { + this.setData({ submitting: false }) + } + }, + onGoHome() { + this.clearDraft() wx.switchTab({ url: '/pages/index/index' }) }, }) diff --git a/src/pages/actAdd/index.wxml b/src/pages/actAdd/index.wxml index be2ba70..f6ec8be 100644 --- a/src/pages/actAdd/index.wxml +++ b/src/pages/actAdd/index.wxml @@ -26,19 +26,18 @@ 活动头图 * - + 点击上传 + + + + + + @@ -65,13 +64,13 @@ 活动时间 * - + {{startTime || '请选择开始时间'}} - + {{endTime || '请选择结束时间'}} @@ -105,13 +104,13 @@ 活动等级 - {{item}} + {{item.name}} @@ -120,19 +119,22 @@ 活动分类 - {{item}} + {{item.name}} - 活动地点 + + 活动地点 + * + 报名时间 * - + {{registerStartTime || '请选择开始时间'}} @@ -245,7 +247,7 @@ - + {{registerEndTime || '请选择结束时间'}} @@ -361,7 +363,7 @@ 签到时间 * - + {{checkinStartTime || '请选择签到开始时间'}} @@ -369,7 +371,7 @@ - + {{checkinEndTime || '请选择签到结束时间'}} @@ -424,9 +426,17 @@ 议程时间 * - + - {{item.time || '请选择时间'}} + + {{item.agendaTime || '请选择时间'}} + @@ -477,15 +487,4 @@ - - - - - - - 提交成功 - 活动已提交审核,请耐心等待 - 返回首页 - - diff --git a/src/pages/actAddResult/index.ts b/src/pages/actAddResult/index.ts index 067b6bb..2950c8e 100644 --- a/src/pages/actAddResult/index.ts +++ b/src/pages/actAddResult/index.ts @@ -1,8 +1,52 @@ -const _app = getApp(); +const app = getApp() Page({ - data: {}, - onLoad() {}, -}); + data: { + activityId: 0, + status: '', // pending | draft + qrCodeUrl: '', // 公众号二维码 URL + }, + + onLoad(options: { id?: string; status?: string }) { + const activityId = options.id ? Number(options.id) : 0 + const status = options.status || '' + + this.setData({ + activityId, + status, + }) + + app.waitLogin({ type: 1 }).then(() => { + // 获取用户信息,包括公众号二维码 + this.getUserProfile() + }) + }, + + // 获取用户信息 + async getUserProfile() { + const res = await wx.ajax({ + url: '/me/profile', + method: 'GET', + }) + + this.setData({ + qrCodeUrl: res.wechatSubscribe.qrCodeUrl, + }) + }, + + // 继续发布 + handleContinue() { + wx.redirectTo({ + url: '/pages/actAdd/index', + }) + }, + + // 返回活动页 + handleBack() { + wx.switchTab({ + url: '/pages/act/index', + }) + }, +}) export {} diff --git a/src/pages/actAddResult/index.wxml b/src/pages/actAddResult/index.wxml index ef481c0..2b5a240 100644 --- a/src/pages/actAddResult/index.wxml +++ b/src/pages/actAddResult/index.wxml @@ -2,12 +2,12 @@ 申请提交成功 我们将会在 7 个工作日内完成审核,结果将通过公 众号发送给你,请注意查收~ - 继续发布 + 继续发布 返回活动页 关注xxx公众号 - + 长按识别二维码 diff --git a/src/pages/actDetail/index.scss b/src/pages/actDetail/index.scss index b0023c8..7a6e550 100644 --- a/src/pages/actDetail/index.scss +++ b/src/pages/actDetail/index.scss @@ -13,7 +13,7 @@ page { .page { min-height: 100vh; box-sizing: border-box; - padding-bottom: 280rpx; + padding-bottom: 320rpx; .info-card { margin: 349rpx 30rpx 0; padding: 48rpx 30rpx 40rpx; @@ -83,9 +83,9 @@ page { .row { margin-top: 34rpx; display: flex; - align-items: center; gap: 12rpx; .icon { + margin-top: 4rpx; flex-shrink: 0; width: 36rpx; height: 36rpx; @@ -418,6 +418,35 @@ page { } .upload-list { margin-top: 20rpx; + display: flex; + flex-wrap: wrap; + gap: 16rpx; + .preview-list { + display: contents; + .preview-item { + position: relative; + width: 108rpx; + height: 108rpx; + border-radius: 16rpx; + overflow: hidden; + .p-img { + display: block; + width: 100%; + height: 100%; + border-radius: 16rpx; + } + .close { + padding: 10rpx; + position: absolute; + top: 0; + right: 0; + background-color: rgba(0, 0, 0, 0.6); + color: #fff; + font-size: 20rpx; + border-radius: 0 16rpx 0 16rpx; + } + } + } .upload { width: 108rpx; height: 108rpx; diff --git a/src/pages/actDetail/index.ts b/src/pages/actDetail/index.ts index 6f0d4c8..f529a33 100644 --- a/src/pages/actDetail/index.ts +++ b/src/pages/actDetail/index.ts @@ -1,33 +1,510 @@ -const _app = getApp() +const app = getApp() + +interface IActivityDetail { + id: number + name: string + type: number + typeOther: string + summary: string + description: string + regType: number + regCondition: string + contactName: string + contactPhone: string + mainImages: string[] + detailImages: string[] + regStartAt: string + regEndAt: string + startAt: string + endAt: string + location: string + organizer: string + status: string + checkinType: number + checkinStartAt: string + checkinEndAt: string + quota: number + regCount: number + viewUserCount: number + viewCount: number + checkinCount: number + commentCount: number + shareCount: number + collectCount: number + tags: string[] + levelId: number + levelName: string + categoryIds: number[] + categoryNames: string[] + isRegistered: boolean + isCheckedIn: boolean + isFavorited: boolean + isReviewed: boolean + countdownSeconds: number + registrationList: Array<{ + userId: number + avatarUrl: string + nickname: string + realName: string + registeredAt: string + }> + agendas: Array<{ + id: number + agendaDate: string + agendaTime: string + title: string + description: string + sort: number + }> +} + +interface IReviewItem { + id: number + userId: number + nickname: string + avatarUrl: string + rating: string + content: string + images: string[] + isAnonymous: boolean + likeCount: number + isLiked: boolean + createdAt: string + auditStatus: string +} + +interface IPagination { + page: number + pageSize: number + total: number + totalPages: number +} Page({ data: { + activityId: 0, + detail: null as IActivityDetail | null, + loading: true, + + // 倒计时 + countdownSeconds: 0, + timeData: { days: 0, hours: 0, minutes: 0, seconds: 0 }, + + // 弹窗 popupShow: false, - popupType: 'popup1', // 签到成功弹窗 + popupType: 'popup1', popupParams: {} as any, + // 评论 commentShow: false, + commentRating: 5, + commentContent: '', + commentImages: [] as Array<{ + uid: string + url: string + type: string + name: string + size: number + status: 'pending' | 'uploading' | 'success' | 'error' + progress: number + }>, + commentAnonymous: false, + + // 评价列表 + reviewList: [] as IReviewItem[], + reviewPagination: { + page: 1, + pageSize: 10, + total: 0, + totalPages: 0, + } as IPagination, + reviewLoading: false, }, - onLoad() {}, - handlePopupOk() { - const { popupType } = this.data - if (popupType === 'argument') { + + onLoad(options: { id?: string }) { + const activityId = options.id ? Number(options.id) : 0 + if (!activityId) { + wx.showToast({ title: '活动不存在', icon: 'error' }) + setTimeout(() => wx.navigateBack(), 1500) + return } + + this.setData({ activityId }) + + app.waitLogin({ type: 1 }).then(() => { + this.fetchActivityDetail() + this.fetchReviewList() + }) }, - handlePopupCancel() { - const { popupType } = this.data - if (popupType === 'conformBindDoctorConform') { + + // 获取活动详情 + async fetchActivityDetail() { + try { + const res = await wx.ajax({ + url: `/activity/detail?id=${this.data.activityId}`, + method: 'GET', + data: {}, + }) + if (res) { + this.setData({ + detail: res, + countdownSeconds: res.countdownSeconds || 0, + loading: false, + }) + } + } catch (err) { + console.error('获取活动详情失败:', err) + this.setData({ loading: false }) + } + }, + + // 获取评价列表 + async fetchReviewList(isRefresh = false) { + if (this.data.reviewLoading) return + + const { reviewPagination } = this.data + const page = isRefresh ? 1 : reviewPagination.page + + this.setData({ reviewLoading: true }) + + try { + const res = await wx.ajax({ + url: `/activity/review-list?activityId=${this.data.activityId}`, + method: 'GET', + data: { page, pageSize: reviewPagination.pageSize }, + }) + if (res) { + const newList = isRefresh ? res.list : [...this.data.reviewList, ...res.list] + this.setData({ + reviewList: newList, + reviewPagination: { + page: res.pagination?.page || page, + pageSize: res.pagination?.pageSize || reviewPagination.pageSize, + total: res.pagination?.total || 0, + totalPages: res.pagination?.totalPages || 0, + }, + }) + } + } catch (err) { + console.error('获取评价列表失败:', err) + } finally { + this.setData({ reviewLoading: false }) } + }, + + // 倒计时变化 + handleTimeChange(e: WechatMiniprogram.CustomEvent) { + this.setData({ timeData: e.detail }) + }, + + // 倒计时结束 + handleTimeFinish() { + this.setData({ countdownSeconds: 0 }) + this.fetchActivityDetail() + }, + + // 一键报名 + async handleRegister() { + const { detail } = this.data + if (!detail) return + + // 检查登录状态 + const accessToken = app.globalData.accessToken + if (!accessToken) { + wx.showToast({ title: '请先登录', icon: 'error' }) + return + } + + try { + wx.showLoading({ title: '报名中...' }) + const res = await wx.ajax({ + url: `/activity/register?id=${this.data.activityId}`, + method: 'POST', + data: {}, + }) + wx.hideLoading() + + if (res) { + wx.showToast({ title: '报名成功', icon: 'success' }) + // 更新状态 + this.setData({ + detail: { + ...detail, + isRegistered: true, + regCount: detail.regCount + 1, + }, + }) + // 跳转到报名成功页面 + wx.navigateTo({ + url: `/pages/actResult/index?id=${this.data.activityId}`, + }) + } + } catch (err: any) { + wx.hideLoading() + const message = err?.message || '报名失败' + wx.showToast({ title: message, icon: 'error' }) + } + }, + + // 签到 + async handleCheckin() { + const { detail } = this.data + if (!detail) return + + // 检查登录状态 + const accessToken = app.globalData.accessToken + if (!accessToken) { + wx.showToast({ title: '请先登录', icon: 'error' }) + return + } + + try { + wx.showLoading({ title: '签到中...' }) + const res = await wx.ajax({ + url: `/activity/checkin?id=${this.data.activityId}`, + method: 'POST', + data: {}, + }) + wx.hideLoading() + + if (res) { + // 显示签到成功弹窗 + this.setData({ + popupShow: true, + popupType: 'checkinSuccess', + popupParams: { checkedAt: res.checkedAt }, + }) + // 更新状态 + this.setData({ + detail: { + ...detail, + isCheckedIn: true, + checkinCount: detail.checkinCount + 1, + }, + }) + } + } catch (err: any) { + wx.hideLoading() + const message = err?.message || '签到失败' + wx.showToast({ title: message, icon: 'error' }) + } + }, + + // 分享 + async handleShare() { + const { detail } = this.data + if (!detail) return + + // 上报分享 + try { + await wx.ajax({ + url: `/activity/share?id=${this.data.activityId}`, + method: 'POST', + data: { channel: 'friend' }, + }) + } catch (err) { + console.error('上报分享失败:', err) + } + }, + + // 打开评论弹窗 + handleOpenComment() { this.setData({ - popupShow: false, - popupType: 'i', + commentShow: true, + commentRating: 5, + commentContent: '', + commentImages: [], + commentAnonymous: false, + }) + }, + + // 评论评分变化 + onCommentRatingChange(e: WechatMiniprogram.CustomEvent) { + this.setData({ commentRating: e.detail }) + }, + + // 评论内容变化 + onCommentContentChange(e: WechatMiniprogram.TextareaInput) { + this.setData({ commentContent: e.detail.value }) + }, + + // 评论图片上传成功 + onCommentImageSuccess(e: WechatMiniprogram.CustomEvent) { + const { file } = e.detail + console.log('上传成功', file) + // 添加上传成功的图片到列表 + this.setData({ + commentImages: [...this.data.commentImages, file], }) }, + + // 评论图片上传失败 + onCommentImageError(e: WechatMiniprogram.CustomEvent) { + const { file, error } = e.detail + console.log('上传失败', file, error) + wx.showToast({ title: '图片上传失败', icon: 'none' }) + }, + + // 删除评论图片 + onRemoveCommentImage(e: WechatMiniprogram.TouchEvent) { + const index = e.currentTarget.dataset.index + const commentImages = [...this.data.commentImages] + commentImages.splice(index, 1) + this.setData({ commentImages }) + }, + + // 评论匿名切换 + onCommentAnonymousChange(e: WechatMiniprogram.CustomEvent) { + this.setData({ commentAnonymous: e.detail.value }) + }, + + // 提交评论 + async handleSubmitComment() { + const { commentRating, commentContent, commentImages, commentAnonymous } = this.data + + if (!commentContent.trim()) { + wx.showToast({ title: '请输入评价内容', icon: 'error' }) + return + } + + try { + wx.showLoading({ title: '提交中...' }) + const res = await wx.ajax({ + url: `/activity/submit-review?id=${this.data.activityId}`, + method: 'POST', + data: { + activityId: this.data.activityId, + rating: commentRating, + content: commentContent, + images: commentImages.map((img) => img.url), + isAnonymous: commentAnonymous, + }, + }) + wx.hideLoading() + + if (res) { + wx.showToast({ title: '评价成功', icon: 'success' }) + this.setData({ + commentShow: false, + detail: { + ...this.data.detail!, + isReviewed: true, + commentCount: this.data.detail!.commentCount + 1, + }, + }) + // 刷新评价列表 + this.fetchReviewList(true) + } + } catch (err: any) { + wx.hideLoading() + const message = err?.message || '评价失败' + wx.showToast({ title: message, icon: 'error' }) + } + }, + + // 关闭评论弹窗 onCommentClose() { + this.setData({ commentShow: false }) + }, + + // 点赞评价 + async handleLikeReview(e: WechatMiniprogram.TouchEvent) { + const reviewId = e.currentTarget.dataset.id + const { reviewList } = this.data + + // 检查登录状态 + const accessToken = app.globalData.accessToken + if (!accessToken) { + wx.showToast({ title: '请先登录', icon: 'error' }) + return + } + + try { + const res = await wx.ajax({ + url: `/activity/toggle-review-like`, + method: 'POST', + data: { + reviewId, + }, + }) + + if (res) { + // 更新评价列表中的点赞状态 + const updatedList = reviewList.map((item) => { + if (item.id === reviewId) { + return { + ...item, + isLiked: res.isLiked, + likeCount: res.likeCount, + } + } + return item + }) + this.setData({ reviewList: updatedList }) + } + } catch (err: any) { + const message = err?.message || '操作失败' + wx.showToast({ title: message, icon: 'error' }) + } + }, + + // 弹窗确认 + handlePopupOk() { + this.setData({ + popupShow: false, + popupType: 'popup1', + }) + }, + + // 弹窗取消 + handlePopupCancel() { this.setData({ - commentShow: false, + popupShow: false, + popupType: 'popup1', }) }, + + // 分享给朋友 + onShareAppMessage() { + const { detail } = this.data + if (!detail) return {} + + // 上报分享 + this.handleShare() + + return { + title: detail.name, + path: `/pages/actDetail/index?id=${this.data.activityId}`, + imageUrl: detail.mainImages[0] || '', + } + }, + + // 获取活动状态文本 + getStatusText(status: string): string { + const statusMap: Record = { + draft: '草稿', + pending: '待审核', + approved: '已通过', + registering: '报名中', + running: '进行中', + ended: '已结束', + cancelled: '已取消', + rejected: '已拒绝', + } + return statusMap[status] || status + }, + + // 获取评分文本 + getRatingText(rating: number): string { + if (rating >= 4.5) return '非常满意' + if (rating >= 4) return '满意' + if (rating >= 3) return '一般' + if (rating >= 2) return '不满意' + return '非常不满意' + }, + handleBack() { + wx.navigateBack() + }, }) export {} diff --git a/src/pages/actDetail/index.wxml b/src/pages/actDetail/index.wxml index df3161b..cd98e53 100644 --- a/src/pages/actDetail/index.wxml +++ b/src/pages/actDetail/index.wxml @@ -1,123 +1,124 @@ - + 详情 - 深职大第十五届校园歌手大赛 + {{detail.name}} - - - + - 1345人已报名 + {{detail.regCount}}人已报名 - 134次 + {{detail.viewCount}}次 - 校园活动 - 学生会 + {{item.name}} - + - 报名时间 2026.5.30 - 2026.6.30. + 报名时间 {{detail.regStartAt}} - {{detail.regEndAt}} - 活动时间 2026.7.1 19:00 - 2026.7.1 21:00 + 活动时间 {{detail.startAt}} - {{detail.endAt}} - 活动地点 留仙洞校区音乐厅 + 活动地点 {{detail.location}} - + - 主办方 学生会 + 主办方 {{detail.organizer}} - + + 活动议程 - - 6月24日 + + {{item.agendaDate}} - - 19:00 + + {{item.agendaTime}} - 开场致辞 - 主持人开场,介绍比赛规则及评委 + {{item.title}} + {{item.description}} - + + 活动介绍 - + + - 精彩评论 (21条) - + 精彩评论 ({{detail.commentCount}}条) + - + - 李可可 + {{item.isAnonymous ? '匿名用户' : item.nickname}} - - 非常满意 + + {{ item.rating }} - - 本次活动组织有序,流程顺畅,现场氛围良好。活动内容丰富、安排合理,服务贴心到位,整体体验良好。歌手们的实力都很强! - - - - + {{item.content}} + + + - 2026-01-01 12:00:00 + {{item.createdAt}} - - - 34 - - - - 回复 + + + {{item.likeCount}} + - 去评论 - 我要报名 + 去评论 + 我要报名 + + 签到 + + 已报名 + - + @@ -157,28 +167,55 @@ 评分 - - 4.5 + + {{commentRating}} - 非常满意 - + - + + + + + + + + + - + 匿名评价 你的头像、昵称将隐藏 - 取消 - 发布 + 取消 + 发布 diff --git a/src/pages/actResult/index.ts b/src/pages/actResult/index.ts index 067b6bb..541290e 100644 --- a/src/pages/actResult/index.ts +++ b/src/pages/actResult/index.ts @@ -1,8 +1,83 @@ -const _app = getApp(); +const app = getApp() + +interface IActivityItem { + id: number + name: string + mainImages: string[] + startAt: string + endAt: string + location: string + status: string + regCount: number +} Page({ - data: {}, - onLoad() {}, -}); + data: { + activityId: 0, + detail: null as any, + recommendList: [] as IActivityItem[], + }, + + onLoad(options: { id?: string }) { + const activityId = options.id ? Number(options.id) : 0 + this.setData({ activityId }) + + app.waitLogin({ type: 0 }).then(() => { + this.fetchActivityDetail() + this.fetchRecommendList() + }) + }, + + // 获取活动详情 + async fetchActivityDetail() { + try { + const res = await wx.ajax({ + url: `/activity/detail?id=${this.data.activityId}`, + method: 'GET', + data: {}, + }) + if (res) { + this.setData({ detail: res }) + } + } catch (err) { + console.error('获取活动详情失败:', err) + } + }, + + // 获取推荐活动列表 + async fetchRecommendList() { + try { + const res = await wx.ajax({ + url: '/activity/list', + method: 'GET', + data: { + page: 1, + pageSize: 3, + isRecommended: true, + }, + }) + if (res && res.list) { + this.setData({ recommendList: res.list }) + } + } catch (err) { + console.error('获取推荐活动列表失败:', err) + } + }, + + // 返回活动页 + handleBack() { + wx.switchTab({ + url: '/pages/act/index', + }) + }, + + // 查看活动详情 + handleDetail(e: WechatMiniprogram.TouchEvent) { + const id = e.currentTarget.dataset.id + wx.navigateTo({ + url: `/pages/actDetail/index?id=${id}`, + }) + }, +}) -export {} +export {} \ No newline at end of file diff --git a/src/pages/actResult/index.wxml b/src/pages/actResult/index.wxml index 61761fc..d09d5b1 100644 --- a/src/pages/actResult/index.wxml +++ b/src/pages/actResult/index.wxml @@ -2,35 +2,33 @@ 报名成功 - 活动将于2026年7月1日19:00开始, 请记得准时参加 + 活动将于{{detail.startAt}}开始,请记得准时参加 - + - 返回活动页 + 返回活动页 - + 你可能感兴趣的活动 - + - 进行中 - - 128人已报名 + {{item.status === 'registering' ? '报名中' : item.status === 'running' ? '进行中' : '已结束'}} + + {{item.regCount}}人已报名 - - 深职大第十五届校园歌手大赛深职大第十五届校园歌手大深职大第十五届校园歌手大深职大第十五届校园歌手大赛赛赛 - + {{item.name}} - 2026.04.01-2026.05.30 + {{item.startAt}} - {{item.endAt}} - 留仙洞校区音乐厅 + {{item.location}} - + \ No newline at end of file diff --git a/src/pages/login/index.ts b/src/pages/login/index.ts index fbf1963..e523f89 100644 --- a/src/pages/login/index.ts +++ b/src/pages/login/index.ts @@ -97,27 +97,21 @@ Page({ wx.hideLoading() this.setData({ loading: false }) - const { accessToken, expireIn, needBind, user } = response + const { accessToken, needBind, user } = response // 存储 accessToken app.globalData.accessToken = accessToken - app.globalData.tokenExpireIn = expireIn + app.globalData.needBind = needBind // 存储用户信息 if (user) { app.globalData.userInfo = user } - // 更新 initLoginInfo - app.globalData.initLoginInfo = { - needBind, - user, - } - // 绑定成功,跳转到目标页面 wx.showToast({ title: '绑定成功', - icon: 'success', + icon: 'none', }) setTimeout(() => { diff --git a/src/pages/schedule/index.scss b/src/pages/schedule/index.scss index a34ae29..b1b5b3a 100644 --- a/src/pages/schedule/index.scss +++ b/src/pages/schedule/index.scss @@ -201,110 +201,130 @@ } .format2 { - } + margin-top: 30rpx; + background: #fff; + border-radius: 24rpx; + box-shadow: 0rpx 15rpx 30rpx 0rpx rgba(74, 172, 219, 0.09); + overflow: hidden; - .format3 { - /* 外层容器 */ - .schedule-wrap { - border: 1rpx solid #eee; - border-radius: 16rpx; - overflow: hidden; - } - /* 表头横向滚动 */ - .header-scroll { - width: 100%; - } - .header-row { - display: flex; - } - /* 左侧时间列统一宽度 */ - .time-col { - width: 160rpx; - flex-shrink: 0; - } - .header-time { - display: flex; - align-items: center; - justify-content: center; - border-right: 1rpx solid #eee; - border-bottom: 1rpx solid #eee; - color: #999; - } - /* 每一天的列宽统一 */ - .day-col { - width: 120rpx; - flex-shrink: 0; - } - .header-day { - text-align: center; - padding: 16rpx 0; - border-right: 1rpx solid #eee; - border-bottom: 1rpx solid #eee; - } - .header-day .week { - font-size: 26rpx; - color: #666; - } - .header-day .date { - font-size: 30rpx; - margin-top: 4rpx; - } - /* 主体横向+纵向滚动 */ - .body-scroll { - height: calc(100vh - 160rpx); - } - .body-row { - display: flex; - } - /* 左侧课时列表 */ - .body-time { - border-right: 1rpx solid #eee; - } - .time-item { - height: 140rpx; /* 单节高度,和课程网格行高一致 */ - padding: 10rpx; - border-bottom: 1rpx solid #eee; + /* 顶部表头(吸顶) */ + .schedule-header { + position: sticky; + top: 0; + z-index: 10; display: flex; - flex-direction: column; - justify-content: center; align-items: center; - color: #666; - font-size: 24rpx; - } - .section-name { - font-weight: 500; - } - .time-range { - font-size: 22rpx; - color: #999; - margin-top: 6rpx; - } - /* 课程网格核心:CSS Grid */ - .grid-container { - display: flex; - } - .grid-day { - display: grid; - grid-template-rows: repeat(9, 140rpx); /* 9节课,每行140rpx */ - border-right: 1rpx solid #eee; - position: relative; - } - /* 课程卡片样式 */ - .course-card { - margin: 8rpx; - border-radius: 8rpx; - padding: 10rpx; - color: #333; - font-size: 24rpx; - } - .course-name { - font-weight: 500; - line-height: 1.4; + border-bottom: 1rpx solid #f0f0f0; + background-color: #fff; /* 吸顶时需要背景色 */ + .header-time { + width: 100rpx; + flex-shrink: 0; + text-align: center; + font-size: 22rpx; + color: #94a3b8; + padding: 20rpx 0; + border-right: 1rpx solid #f0f0f0; + } + .header-days { + flex: 1; + display: flex; + .day-header { + flex: 1; /* 自适应宽度 */ + text-align: center; + padding: 16rpx 0; + &.active { + background-color: rgba(74, 184, 253, 1); + .day-week, + .day-date { + color: #fff; + } + } + .day-week { + font-size: 22rpx; + color: #94a3b8; + } + .day-date { + margin-top: 4rpx; + font-size: 28rpx; + color: #1e293b; + } + } + } } - .course-loc { - margin-top: 8rpx; - font-size: 22rpx; - opacity: 0.8; + + /* 课程主体区域 */ + .schedule-body { + height: calc(100vh - 500rpx); + .body-row { + display: flex; + position: relative; + } + /* 左侧时间列 */ + .body-time { + width: 100rpx; + flex-shrink: 0; + border-right: 1rpx solid #f0f0f0; + .time-slot { + height: 140rpx; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + border-bottom: 1rpx solid #f5f5f5; + .slot-name { + font-size: 24rpx; + color: #1e293b; + font-weight: 500; + } + .slot-range { + margin-top: 8rpx; + font-size: 20rpx; + color: #94a3b8; + white-space: pre-line; + text-align: center; + line-height: 1.4; + } + } + } + /* 右侧课程网格 */ + .body-grid { + flex: 1; + display: flex; + position: relative; + .day-column { + flex: 1; /* 自适应宽度,与表头保持一致 */ + border-right: 1rpx solid #f5f5f5; + position: relative; + min-height: 1260rpx; /* 9节 * 140rpx */ + /* 网格单元格 */ + .grid-cell { + height: 140rpx; + border-bottom: 1rpx solid #f5f5f5; + } + .course-block { + position: absolute; + left: 4rpx; + right: 4rpx; + padding: 12rpx; + border-radius: 12rpx; + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: center; + .course-title { + font-size: 22rpx; + font-weight: 500; + line-height: 1.4; + word-break: break-all; + } + .course-loc { + margin-top: 20rpx; + font-size: 18rpx; + line-height: 1.3; + } + } + } + } } } } diff --git a/src/pages/schedule/index.ts b/src/pages/schedule/index.ts index 89d1a56..a01b057 100644 --- a/src/pages/schedule/index.ts +++ b/src/pages/schedule/index.ts @@ -2,8 +2,6 @@ const _app = getApp() Page({ data: { - scrollX: 0, // 横向滚动偏移量 - isSyncing: false, // 同步锁,防止双向滚动死循环 todayIndex: 1, // 今天对应的索引(周二=1) // 左侧9节课时间段 sectionList: [ @@ -120,30 +118,6 @@ Page({ ], }, onLoad() {}, - // 滑动表头时,同步主体滚动 - onHeaderScroll(e) { - if (this.data.isSyncing) return - this.setData({ - isSyncing: true, - scrollX: e.detail.scrollLeft, - }) - // 延迟解锁同步锁,避免双向触发循环 - setTimeout(() => { - this.setData({ isSyncing: false }) - }, 80) - }, - - // 滑动课程主体时,同步表头滚动 - onBodyScroll(e) { - if (this.data.isSyncing) return - this.setData({ - isSyncing: true, - scrollX: e.detail.scrollLeft, - }) - setTimeout(() => { - this.setData({ isSyncing: false }) - }, 80) - }, }) export {} diff --git a/src/pages/schedule/index.wxml b/src/pages/schedule/index.wxml index a418e39..7fb8351 100644 --- a/src/pages/schedule/index.wxml +++ b/src/pages/schedule/index.wxml @@ -61,7 +61,7 @@ - + 时间 @@ -73,7 +73,7 @@ - + @@ -85,6 +85,9 @@ + + + - - - - - - - 时间 - - {{item.week}} - {{item.date}} - - - - - - - - - - 第{{item.index}}节 - {{item.time}} - - - - - - {{item.name}} - {{item.loc}} - - - - - - - diff --git a/typings/index.d.ts b/typings/index.d.ts index ee96425..62e9382 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -26,27 +26,19 @@ interface IAppOption { Timestamp: number waitBindDoctorId: string - initLoginInfo: Partial<{ - isLogin: 0 | 1 - isReg: 0 | 1 - loginType: 1 | 2 - needBind?: boolean - user?: UserInfo - }> // JWT 令牌相关 accessToken?: string tokenExpireIn?: number openidSession?: string // 临时凭证(用于绑定) - userInfo?: UserInfo + userInfo?: UserInfo | {} loginRedirectUrl?: string // 登录后返回的页面路径 [propName: string]: any } getUserInfo: (type?: 0 | 1 | 2) => Promise startLogin: (callback?: () => void) => void - waitLogin: (params?: { type?: 0 | 1 | 2 | 'any' }) => Promise - checkLoginType: (type: 0 | 1 | 2 | 'any') => boolean + waitLogin: (params?: { type?: 0 | 1 }) => Promise [propName: string]: any }