From 44e789f595a40e9dd206cd85ae9628affd671110 Mon Sep 17 00:00:00 2001 From: ywyonui Date: Mon, 18 Aug 2025 20:47:50 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E9=80=89=E8=AF=BE=E7=9B=B8?= =?UTF-8?q?=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/base/server.ts | 13 - src/api/base/xkApi.ts | 48 ++ src/pages.json | 39 +- .../base/groupTeaching/photoXkkcDetail.vue | 419 ------------------ .../groupTeaching_bak/studentRollCall.vue | 410 ----------------- src/pages/base/groupTeaching_bak/zhujiao.vue | 57 --- .../base/groupTeaching_bak/zhujiaoDetails.vue | 165 ------- src/pages/base/service/index.vue | 4 +- src/pages/components/dmPs/INTEGRATION.md | 225 ++++++++++ src/pages/components/dmPs/README.md | 264 +++++++++++ src/pages/components/dmPs/index.vue | 395 +++++++++++++++++ src/pages/system/login/login.vue | 2 +- src/pages/view/routine/jc/components/dm.vue | 82 ++++ .../routine/xk/dm.vue} | 320 +++++++++---- .../routine/xk/dmIndex.vue} | 80 +--- src/pages/view/routine/xk/dmList.vue | 384 ++++++++++++++++ .../routine/xk/dmXsList.vue} | 339 +++++++------- .../routine/xk}/xkList.vue | 6 +- .../routine/xk}/xkkcDetail.vue | 0 19 files changed, 1840 insertions(+), 1412 deletions(-) create mode 100644 src/api/base/xkApi.ts delete mode 100644 src/pages/base/groupTeaching/photoXkkcDetail.vue delete mode 100644 src/pages/base/groupTeaching_bak/studentRollCall.vue delete mode 100644 src/pages/base/groupTeaching_bak/zhujiao.vue delete mode 100644 src/pages/base/groupTeaching_bak/zhujiaoDetails.vue create mode 100644 src/pages/components/dmPs/INTEGRATION.md create mode 100644 src/pages/components/dmPs/README.md create mode 100644 src/pages/components/dmPs/index.vue rename src/pages/{base/groupTeaching/dmXkkcDetail.vue => view/routine/xk/dm.vue} (61%) rename src/pages/{base/groupTeaching/dmXkList.vue => view/routine/xk/dmIndex.vue} (89%) create mode 100644 src/pages/view/routine/xk/dmList.vue rename src/pages/{base/groupTeaching/dmXkkcRecord.vue => view/routine/xk/dmXsList.vue} (50%) rename src/pages/{base/groupTeaching => view/routine/xk}/xkList.vue (98%) rename src/pages/{base/groupTeaching => view/routine/xk}/xkkcDetail.vue (100%) diff --git a/src/api/base/server.ts b/src/api/base/server.ts index 244a9cf..6687e6c 100644 --- a/src/api/base/server.ts +++ b/src/api/base/server.ts @@ -94,24 +94,11 @@ export const jsdXkListApi = async (params: any) => { return await get("/mobile/js/xk/list", params); }; -/** - * 获取当前学期教师上课课程列表 - */ -export const getCurrentSemesterTeacherCoursesApi = async (jsId?: string) => { - const params = jsId ? { jsId } : {}; - return await get("/api/xkkc/getCurrentSemesterTeacherCourses", params); -}; - // 选课列表 export const jsdXkkcSaveApi = async (params: any) => { return await post("/api/xkkc/save", params); }; -// 选课学生列表 -export const jsdXkXsListApi = async (params: any) => { - return await get("/mobile/js/xkxs/list", params); -}; - // 获取班级学生考试成绩(按科目) export const jsdBjKscjKmApi = async (params: any) => { return await get("/mobile/js/kscj/bjKm", params); diff --git a/src/api/base/xkApi.ts b/src/api/base/xkApi.ts new file mode 100644 index 0000000..d7e3b0d --- /dev/null +++ b/src/api/base/xkApi.ts @@ -0,0 +1,48 @@ +import { get, post } from "@/utils/request"; + +/** + * 根据教师查询选课列表 + */ +export const xkListByJsIdApi = async (params: any) => { + return await get("/api/xk/findXkListByJsId", params); +}; + +/** + * 根据教师查询选课课程列表 + */ +export const xkkcListByJsIdApi = async (params: any) => { + return await get("/api/xkkc/getXkkcListByJsId", params); +}; + +/** + * 查询学生点名集合 - 匹配后端XkkcApiController.findXkkcList + */ +export const findXkkcListApi = async (params: any) => { + return await get("/api/xkkc/findXkkcList", params); +}; + +/** + * 获取选课点名分页 + */ +export const getXkDmPageApi = async (params: any) => { + return await get('/api/xkDm/findPage', params) +} + +/** + * 获取选课点名学生分页 + */ +export const getXkDmXsPageApi = async (params: any) => { + return await get('/api/xkDmXs/findPage', params) +} + +// 选课学生列表 +export const getWaitDmXsListApi = async (params: any) => { + return await get("/api/xkDmXs/getWaitDmXsList", params); +}; + +/** + * 提交选课点名 + */ +export const submitXkDmApi = async (data: any) => { + return await post('/api/xkDm/save', data) +} diff --git a/src/pages.json b/src/pages.json index 027cc14..591fa5d 100644 --- a/src/pages.json +++ b/src/pages.json @@ -486,19 +486,6 @@ "enablePullDownRefresh": false } }, - { - "path": "pages/base/groupTeaching/xkList", - "style": { - "navigationBarTitleText": "选课列表", - "enablePullDownRefresh": false - } - }, - { - "path": "pages/base/groupTeaching/xkkcDetail", - "style": { - "navigationBarTitleText": "选课课程详情" - } - }, { "path": "pages/view/routine/RengJiaoRengZhi/index", "style": { @@ -514,29 +501,43 @@ } }, { - "path": "pages/base/groupTeaching/dmXkList", + "path": "pages/view/routine/xk/xkList", "style": { - "navigationBarTitleText": "选课列表" + "navigationBarTitleText": "选课列表", + "enablePullDownRefresh": false } }, { - "path": "pages/base/groupTeaching/dmXkkcDetail", + "path": "pages/view/routine/xk/xkkcDetail", + "style": { + "navigationBarTitleText": "选课课程详情" + } + }, + { + "path": "pages/view/routine/xk/dmIndex", + "style": { + "navigationBarTitleText": "点名选课列表" + } + }, + { + "path": "pages/view/routine/xk/dm", "style": { "navigationBarTitleText": "学生点名", "enablePullDownRefresh": false } }, { - "path": "pages/base/groupTeaching/dmXkkcRecord", + "path": "pages/view/routine/xk/dmRecord", "style": { "navigationBarTitleText": "点名记录", "enablePullDownRefresh": false } }, { - "path": "pages/base/groupTeaching/photoXkkcDetail", + "path": "pages/view/routine/xk/dmXsRecord", "style": { - "navigationBarTitleText": "课堂随拍" + "navigationBarTitleText": "点名学生记录", + "enablePullDownRefresh": false } }, { diff --git a/src/pages/base/groupTeaching/photoXkkcDetail.vue b/src/pages/base/groupTeaching/photoXkkcDetail.vue deleted file mode 100644 index 8b5d78f..0000000 --- a/src/pages/base/groupTeaching/photoXkkcDetail.vue +++ /dev/null @@ -1,419 +0,0 @@ - - - - - diff --git a/src/pages/base/groupTeaching_bak/studentRollCall.vue b/src/pages/base/groupTeaching_bak/studentRollCall.vue deleted file mode 100644 index 1bdcc91..0000000 --- a/src/pages/base/groupTeaching_bak/studentRollCall.vue +++ /dev/null @@ -1,410 +0,0 @@ - - - - - diff --git a/src/pages/base/groupTeaching_bak/zhujiao.vue b/src/pages/base/groupTeaching_bak/zhujiao.vue deleted file mode 100644 index 17fed47..0000000 --- a/src/pages/base/groupTeaching_bak/zhujiao.vue +++ /dev/null @@ -1,57 +0,0 @@ - - - - diff --git a/src/pages/base/groupTeaching_bak/zhujiaoDetails.vue b/src/pages/base/groupTeaching_bak/zhujiaoDetails.vue deleted file mode 100644 index fe1b322..0000000 --- a/src/pages/base/groupTeaching_bak/zhujiaoDetails.vue +++ /dev/null @@ -1,165 +0,0 @@ - - - - diff --git a/src/pages/base/service/index.vue b/src/pages/base/service/index.vue index 96bae89..daa9d0f 100644 --- a/src/pages/base/service/index.vue +++ b/src/pages/base/service/index.vue @@ -237,7 +237,7 @@ const sections = reactive([ text: "课程填报", show: true, permissionKey: "routine-kcjs", // 课程介绍权限编码 - path: "/pages/base/groupTeaching/xkList", + path: "/pages/view/routine/xk/xkList", }, { id: "r6", @@ -295,7 +295,7 @@ const sections = reactive([ text: "选课点名", show: true, permissionKey: "routine-kcdm", // 选课点名权限编码 - path: "/pages/base/groupTeaching/dmXkList", + path: "/pages/view/routine/xk/dmIndex", }, { id: "r11", diff --git a/src/pages/components/dmPs/INTEGRATION.md b/src/pages/components/dmPs/INTEGRATION.md new file mode 100644 index 0000000..22de0aa --- /dev/null +++ b/src/pages/components/dmPs/INTEGRATION.md @@ -0,0 +1,225 @@ +# DmPs 组件集成文档 + +## 概述 + +`DmPs` 组件已成功集成到选课点名和就餐点名两个页面中,提供统一的拍照和视频录制功能。 + +## 集成页面 + +### 1. 选课点名页面 +**文件路径**: `zhxy-jsd/src/pages/view/routine/xk/dm.vue` + +**功能特点**: +- 课程现场拍照和视频录制 +- 支持最多9张照片和3个视频 +- 视频最大时长60秒 +- 提交时自动上传媒体文件到服务器 + +**关键代码**: +```vue + +``` + +### 2. 就餐点名页面 +**文件路径**: `zhxy-jsd/src/pages/view/routine/jc/components/dm.vue` + +**功能特点**: +- 就餐现场拍照和视频录制 +- 支持最多9张照片和3个视频 +- 视频最大时长60秒 +- 提交时自动上传媒体文件到服务器 + +**关键代码**: +```vue + +``` + +## 数据结构 + +### 媒体数据对象 +```typescript +interface MediaData { + photoList: Array<{ + url: string; // 照片URL(本地临时路径) + path: string; // 照片路径(本地临时路径) + }>; + videoList: Array<{ + url: string; // 视频URL(本地临时路径) + path: string; // 视频路径(本地临时路径) + }>; +} +``` + +### 上传结果对象 +```typescript +interface UploadResult { + photoUrls: string; // 照片服务器地址,逗号分隔 + videoUrls: string; // 视频服务器地址,逗号分隔 +} +``` + +## 事件处理 + +### 事件处理函数 +两个页面都实现了以下事件处理函数: + +```typescript +// 处理照片变化 +const handlePhotoChange = (photoList: Array<{url: string, path: string}>) => { + console.log('照片列表变化:', photoList); +}; + +// 处理视频变化 +const handleVideoChange = (videoList: Array<{url: string, path: string}>) => { + console.log('视频列表变化:', videoList); +}; + +// 处理照片添加 +const handlePhotoAdd = (photo: {url: string, path: string}) => { + console.log('添加照片:', photo); +}; + +// 处理视频添加 +const handleVideoAdd = (video: {url: string, path: string}) => { + console.log('添加视频:', video); +}; + +// 处理上传完成事件 +const handleUploadComplete = (result: {photoUrls: string, videoUrls: string}) => { + console.log('媒体文件上传完成:', result); + console.log('照片地址:', result.photoUrls); + console.log('视频地址:', result.videoUrls); +}; +``` + +## 提交流程 + +### 选课点名提交流程 +1. 用户点击提交按钮 +2. 验证数据完整性 +3. 如果有媒体文件,调用 `dmPsRef.value.uploadMedia()` +4. 等待上传完成,获取服务器地址 +5. 将地址转换为逗号分隔字符串 +6. 设置到 `dmData.zp`(照片)和 `dmData.sp`(视频) +7. 提交到后端 API + +### 就餐点名提交流程 +1. 用户点击提交按钮 +2. 验证班级和学生数据 +3. 如果有媒体文件,调用 `dmPsRef.value.uploadMedia()` +4. 等待上传完成,获取服务器地址 +5. 将地址转换为逗号分隔字符串 +6. 设置到 `dmData.zp`(照片)和 `dmData.sp`(视频) +7. 提交到后端 API + +## 错误处理 + +### 上传失败处理 +```typescript +try { + if (dmPsRef.value) { + const uploadResult = await dmPsRef.value.uploadMedia(); + photoUrls = uploadResult.photoUrls; + videoUrls = uploadResult.videoUrls; + } +} catch (uploadError) { + console.error('媒体文件上传失败:', uploadError); + uni.showToast({ + title: '媒体文件上传失败,请重试', + icon: 'none' + }); + // 停止提交流程 + return; +} +``` + +### TypeScript 类型安全 +- 使用 `ref(null)` 定义组件引用 +- 添加空值检查 `if (dmPsRef.value)` +- 完整的类型定义和错误处理 + +## 样式适配 + +### 选课点名样式 +- 使用 `BasicLayout` 布局 +- 卡片式设计 +- 响应式网格布局 + +### 就餐点名样式 +- 使用 `section` 分区块设计 +- 卡片式学生列表 +- 固定底部提交按钮 + +## 数据存储 + +### 后端字段映射 +- `zp`: 照片服务器地址(逗号分隔字符串) +- `sp`: 视频服务器地址(逗号分隔字符串) + +### 数据重置 +提交成功后,两个页面都会重置媒体数据: +```typescript +mediaData.value = { + photoList: [], + videoList: [] +}; +``` + +## 使用建议 + +1. **权限管理**: 确保应用有相机和麦克风权限 +2. **网络状态**: 上传前检查网络连接状态 +3. **文件大小**: 注意控制照片和视频的文件大小 +4. **用户体验**: 上传过程中显示加载状态 +5. **错误恢复**: 上传失败时提供重试机制 + +## 扩展功能 + +### 可能的扩展 +1. **批量上传**: 支持同时上传多个文件 +2. **进度显示**: 显示上传进度百分比 +3. **压缩功能**: 自动压缩大文件 +4. **预览功能**: 上传前预览媒体文件 +5. **编辑功能**: 简单的图片编辑功能 + +## 维护说明 + +### 组件更新 +当 `DmPs` 组件更新时,需要同步更新两个页面的: +1. 组件引用 +2. 事件处理函数 +3. 数据结构定义 +4. 错误处理逻辑 + +### 测试要点 +1. 拍照功能测试 +2. 视频录制测试 +3. 文件上传测试 +4. 错误处理测试 +5. 数据提交测试 diff --git a/src/pages/components/dmPs/README.md b/src/pages/components/dmPs/README.md new file mode 100644 index 0000000..afe98af --- /dev/null +++ b/src/pages/components/dmPs/README.md @@ -0,0 +1,264 @@ +# DmPsComponent 拍照视频组件 + +这是一个通用的拍照和视频录制组件,可以在就餐点名和选课点名等场景中复用。 + +## 功能特性 + +- 📸 **拍照功能**:支持相机拍照,可设置最大照片数量 +- 🎥 **视频录制**:支持视频录制,可设置最大视频数量和时长 +- 🖼️ **照片预览**:点击照片可全屏预览 +- 🗑️ **删除功能**:支持删除已拍摄的照片和视频 +- 📤 **文件上传**:支持将本地文件上传到服务器 +- 📱 **响应式设计**:适配不同屏幕尺寸 +- 🎨 **可定制样式**:支持自定义标题、按钮文字等 + +## 使用方法 + +### 基础用法 + +```vue + + + +``` + +### 完整用法(包含上传功能) + +```vue + + + +``` + +## Props 属性 + +| 属性名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| modelValue | Object | - | 双向绑定的媒体数据对象 | +| photoTitle | String | '现场拍照' | 拍照区域标题 | +| photoUploadText | String | '点击拍照' | 拍照按钮文字 | +| maxPhotoCount | Number | 9 | 最大照片数量 | +| videoTitle | String | '现场视频' | 视频区域标题 | +| videoUploadText | String | '点击录制' | 录制按钮文字 | +| maxVideoCount | Number | 3 | 最大视频数量 | +| maxVideoDuration | Number | 60 | 最大视频时长(秒) | + +## Events 事件 + +| 事件名 | 参数 | 说明 | +|--------|------|------| +| update:modelValue | value | 媒体数据变化时触发 | +| photoChange | photoList | 照片列表变化时触发 | +| videoChange | videoList | 视频列表变化时触发 | +| photoAdd | photo | 添加照片时触发 | +| photoDelete | index | 删除照片时触发 | +| videoAdd | video | 添加视频时触发 | +| videoDelete | index | 删除视频时触发 | +| uploadComplete | result | 文件上传完成时触发 | + +## Methods 方法 + +通过 ref 可以调用以下方法: + +| 方法名 | 参数 | 返回值 | 说明 | +|--------|------|--------|------| +| takePhoto | - | - | 触发拍照 | +| recordVideo | - | - | 触发录制视频 | +| deletePhoto | index | - | 删除指定索引的照片 | +| deleteVideo | index | - | 删除指定索引的视频 | +| uploadMedia | - | Promise<{photoUrls: string, videoUrls: string}> | 上传所有媒体文件到服务器 | +| clearAll | - | - | 清空所有媒体数据 | + +## 数据结构 + +### modelValue 对象结构 + +```typescript +interface MediaData { + photoList: Array<{ + url: string; // 照片URL(本地临时路径) + path: string; // 照片路径(本地临时路径) + }>; + videoList: Array<{ + url: string; // 视频URL(本地临时路径) + path: string; // 视频路径(本地临时路径) + }>; +} +``` + +### uploadMedia 返回值结构 + +```typescript +interface UploadResult { + photoUrls: string; // 照片服务器地址,逗号分隔 + videoUrls: string; // 视频服务器地址,逗号分隔 +} +``` + +## 文件上传流程 + +1. **拍照/录制**:用户拍照或录制视频,文件存储在本地临时路径 +2. **预览/删除**:用户可以预览或删除已拍摄的媒体文件 +3. **提交时上传**:调用 `uploadMedia()` 方法,将所有文件上传到服务器 +4. **获取服务器地址**:上传成功后返回服务器文件路径 +5. **存储到数据库**:将服务器路径(逗号分隔)存储到数据库对应字段 + +## 使用场景 + +### 1. 选课点名拍照 + +```vue + +``` + +### 2. 就餐点名拍照 + +```vue + +``` + +### 3. 活动记录拍照 + +```vue + +``` + +## 注意事项 + +1. **权限要求**:使用相机和麦克风需要相应的系统权限 +2. **文件大小**:注意控制照片和视频的文件大小,避免影响性能 +3. **存储空间**:大量媒体文件可能占用较多存储空间 +4. **网络上传**:提交时需要将本地文件上传到服务器 +5. **上传失败处理**:上传失败时会抛出异常,需要妥善处理 +6. **服务器地址格式**:上传成功后返回的是服务器相对路径,需要配合 `imagUrl()` 函数使用 + +## 样式定制 + +组件使用 SCSS 编写,可以通过以下方式定制样式: + +```scss +// 自定义组件样式 +.dm-ps-component { + .section-card { + background: #f8f9fa; + border-radius: 12px; + } + + .section-title { + color: #007aff; + font-weight: bold; + } + + .photo-upload, .video-upload { + border-color: #007aff; + background: rgba(0, 122, 255, 0.1); + } +} +``` diff --git a/src/pages/components/dmPs/index.vue b/src/pages/components/dmPs/index.vue new file mode 100644 index 0000000..1aeb0a6 --- /dev/null +++ b/src/pages/components/dmPs/index.vue @@ -0,0 +1,395 @@ + + + + + diff --git a/src/pages/system/login/login.vue b/src/pages/system/login/login.vue index 2b7191e..b967659 100644 --- a/src/pages/system/login/login.vue +++ b/src/pages/system/login/login.vue @@ -156,7 +156,7 @@ const handleGetCode = async () => { function toHome(data: any) { if (data.type == 1) { uni.reLaunch({ - url: "/pages/base/groupTeaching/zhujiao", + url: "/pages/view/routine/xk/zhujiao", }); } else { uni.switchTab({ diff --git a/src/pages/view/routine/jc/components/dm.vue b/src/pages/view/routine/jc/components/dm.vue index 856f56f..56f1f61 100644 --- a/src/pages/view/routine/jc/components/dm.vue +++ b/src/pages/view/routine/jc/components/dm.vue @@ -193,6 +193,24 @@ + + + + + (null) // 当前教师 const jsZtXzK = ref(false) // 教师状态选择可见 const jsMqXz = ref([0]) // 教师状态默认选择 +// 媒体数据 +const mediaData = ref({ + photoList: [], + videoList: [] +}); +const dmPsRef = ref(null); + // 计算属性 const kTj = computed(() => { return curBj.value && yjfXs.value.length > 0 // 改为检查已缴费学生数量 @@ -312,6 +338,33 @@ const changeNjBj = async (nj: any, bj: any) => { await jzXsLb() }; +// 处理照片变化 +const handlePhotoChange = (photoList: Array<{url: string, path: string}>) => { + console.log('照片列表变化:', photoList); +}; + +// 处理视频变化 +const handleVideoChange = (videoList: Array<{url: string, path: string}>) => { + console.log('视频列表变化:', videoList); +}; + +// 处理照片添加 +const handlePhotoAdd = (photo: {url: string, path: string}) => { + console.log('添加照片:', photo); +}; + +// 处理视频添加 +const handleVideoAdd = (video: {url: string, path: string}) => { + console.log('添加视频:', video); +}; + +// 处理上传完成事件 +const handleUploadComplete = (result: {photoUrls: string, videoUrls: string}) => { + console.log('媒体文件上传完成:', result); + console.log('照片地址:', result.photoUrls); + console.log('视频地址:', result.videoUrls); +}; + const jsXz = (teachers: any[]) => { xzJs.value = teachers.map(teacher => ({ ...teacher, @@ -553,6 +606,28 @@ const tjDm = async () => { jzZt.value = true try { + // 先上传媒体文件 + let photoUrls = ''; + let videoUrls = ''; + + if (mediaData.value.photoList.length > 0 || mediaData.value.videoList.length > 0) { + try { + if (dmPsRef.value) { + const uploadResult = await dmPsRef.value.uploadMedia(); + photoUrls = uploadResult.photoUrls; + videoUrls = uploadResult.videoUrls; + } + } catch (uploadError) { + console.error('媒体文件上传失败:', uploadError); + uni.showToast({ + title: '媒体文件上传失败,请重试', + icon: 'none' + }); + jzZt.value = false; + return; + } + } + // 准备点名数据 const dmData: any = { bjId: curBj.value.key, @@ -561,6 +636,9 @@ const tjDm = async () => { njmc: curNj.value.title, dmJsId: getJs.id || '', // 点名教师ID dmTime: new Date(), + // 媒体文件地址 + zp: photoUrls, // 照片字段,逗号分隔的字符串 + sp: videoUrls, // 视频字段,逗号分隔的字符串 xsList: yjfXs.value.map(student => ({ xsId: student.id, xsXm: student.xm, // 传入学生姓名 @@ -592,6 +670,10 @@ const tjDm = async () => { curBj.value = null xsLb.value = [] xzJs.value = [] + mediaData.value = { + photoList: [], + videoList: [] + }; // 返回上一页 uni.navigateBack() diff --git a/src/pages/base/groupTeaching/dmXkkcDetail.vue b/src/pages/view/routine/xk/dm.vue similarity index 61% rename from src/pages/base/groupTeaching/dmXkkcDetail.vue rename to src/pages/view/routine/xk/dm.vue index ee7ebf1..789fd96 100644 --- a/src/pages/base/groupTeaching/dmXkkcDetail.vue +++ b/src/pages/view/routine/xk/dm.vue @@ -13,19 +13,19 @@ - {{ numInfo.zg }} + {{ dmInfo.zrs }} 总人数 - {{ numInfo.sd }} + {{ dmInfo.sdRs }} 实到 - {{ numInfo.qj }} + {{ dmInfo.qjRs }} 请假 - {{ numInfo.qq }} + {{ dmInfo.qqRs }} 缺勤 @@ -43,19 +43,19 @@ - {{ xs.xsxm }} + {{ xs.xsXm || xs.xsxm }} - {{ xs.xszt }} + {{ getStatusText(xs.xsZt || xs.xszt) }} @@ -75,6 +75,16 @@ + + import {onMounted, ref, computed} from "vue"; import BasicLayout from "@/components/BasicLayout/Layout.vue"; +import DmPsComponent from "@/pages/components/dmPs/index.vue"; import { useUserStore } from "@/store/modules/user"; import { useDataStore } from "@/store/modules/data"; -import { jsdXkXsListApi, jsdXkdmListApi } from "@/api/base/server"; +import { getWaitDmXsListApi, submitXkDmApi, findXkkcListApi } from "@/api/base/xkApi"; import { useDicStore } from "@/store/modules/dic"; import { BASE_IMAGE_URL } from "@/config"; const { findByPid } = useDicStore(); @@ -125,6 +136,12 @@ const { getData, setData } = useDataStore(); const js = computed(() => getJs) const xkkc = computed(() => getData) +const mediaData = ref({ + photoList: [], + videoList: [] +}); +const dmPsRef = ref(null); + const now = dayjs(); let wDay = now.day(); if (wDay === 0) { @@ -135,18 +152,15 @@ const todayInfo = ref({ date: now.format("YYYY-MM-DD"), weekName: wdNameList[wDay - 1] }) -// 人数信息 -const numInfo = ref({ - yd: 18, - sd: 18, - qj: 0, - qq: 0, - zg: 18 -}); -// 学生列表 -const xsList = ref([ -]); +const dmInfo = ref({ + zrs: 0, + sdRs: 0, + qjRs: 0, + qqRs: 0 +}); +// 学生列表 - 匹配后端XkDmXs实体字段 +const xsList = ref([]); // 状态选择相关 const statusPickerVisible = ref(false); @@ -167,10 +181,13 @@ const getImageUrl = (imagePath: string) => { // 获取状态对应的样式类 const getStatusClass = (status: string) => { switch (status) { + case "A": case "正常": return "status-normal"; + case "B": case "请假": return "status-leave"; + case "C": case "缺勤": return "status-absent"; default: @@ -178,6 +195,20 @@ const getStatusClass = (status: string) => { } }; +// 获取状态文本 +const getStatusText = (status: string) => { + switch (status) { + case "A": + return "正常"; + case "B": + return "请假"; + case "C": + return "缺勤"; + default: + return status || "正常"; + } +}; + // 获取学生状态选项 const loadStatusOptions = async () => { try { @@ -193,60 +224,93 @@ const loadStatusOptions = async () => { } } catch (error) { console.error("获取状态选项失败", error); - // 使用默认状态 + // 使用默认状态 - 匹配后端字段 statusOptions.value = [ - {text: "正常", value: "正常"}, - {text: "请假", value: "请假"}, - {text: "缺勤", value: "缺勤"} + {text: "正常", value: "A"}, + {text: "请假", value: "B"}, + {text: "缺勤", value: "C"} ]; } }; +// 加载学生列表 - 匹配后端数据结构 const loadXsList = async () => { - const res = await jsdXkXsListApi({ - xkkcId: xkkc.value.id - }); - if (res && res.resultCode === 1) { - xsList.value = res.result || []; - rebuildNumInfo(); + // 如果新接口失败,尝试使用旧接口 + try { + const res = await getWaitDmXsListApi({ + xkkcId: xkkc.value.id + }); + if (res && res.resultCode === 1) { + dmInfo.value = res.result || {}; + xsList.value = (dmInfo.value.xsList || []).map((dmXs: any) => ({ + id: dmXs.id, + xsId: dmXs.xsId || dmXs.id, + xsXm: dmXs.xsXm || dmXs.xsxm || dmXs.xm, + xsZt: dmXs.xsZt || dmXs.xszt || "A", + qdId: dmXs.qdId, + tx: dmXs.tx || dmXs.xstx || dmXs.avatar, + bjmc: dmXs.bjmc, + njmc: dmXs.njmc, + jzxm: dmXs.jzxm, + jzdh: dmXs.jzdh, + xsxm: dmXs.xsxm || dmXs.xm, + xstx: dmXs.xstx || dmXs.avatar, + xszt: dmXs.xszt || "正常" + })); + rebuildNumInfo(); + } + } catch (fallbackError) { + console.error("备用接口也失败:", fallbackError); + uni.showToast({ + title: "加载学生列表失败", + icon: "none" + }); } }; +// 重新计算人数统计 - 匹配后端字段 const rebuildNumInfo = () => { - let sd = 0; - let qj = 0; - let qq = 0; - // 循环统计状态对应的人数 - for (let i = 0; i < xsList.value.length; i++) { - const xs = xsList.value[i]; - switch (xs.xszt) { - case "正常": - sd++; - break; - case "请假": - qj++; - break; - case "缺勤": - qq++; - break; - default: - break; - } - } - numInfo.value = { - zg: xsList.value.length, - yd: xsList.value.length - qj, - sd: sd, - qj: qj, - qq: qq - }; -} + let sd = 0; + let qj = 0; + let qq = 0; + + // 循环统计状态对应的人数 + for (let i = 0; i < xsList.value.length; i++) { + const xs = xsList.value[i]; + const status = xs.xsZt || xs.xszt; + + switch (status) { + case "A": + case "正常": + sd++; + break; + case "B": + case "请假": + qj++; + break; + case "C": + case "缺勤": + qq++; + break; + default: + sd++; // 默认算作正常 + break; + } + } + dmInfo.value.sdRs = sd; // 实到人数 + dmInfo.value.qjRs = qj; // 请假人数 + dmInfo.value.qqRs = qq; // 缺勤人数 +}; // 打开状态选择器 const openStatusPicker = (xs: any) => { curXs.value = xs; + const currentStatus = xs.xsZt || xs.xszt; + + // 找到当前状态在选项中的索引 for (let i = 0; i < statusOptions.value.length; i++) { - if (statusOptions.value[i].text === xs.xszt) { + if (statusOptions.value[i].value === currentStatus || + statusOptions.value[i].text === currentStatus) { defSel.value = [i]; break; } @@ -262,42 +326,32 @@ const confirmStatus = (e: any) => { ); if (selectedStatus) { - // 更新当前学生状态 - curXs.value.xszt = selectedStatus.text; + // 更新当前学生状态 - 使用后端字段 + curXs.value.xsZt = selectedStatus.value; // 使用代码值 + curXs.value.xszt = selectedStatus.text; // 保留文本值以兼容 } - rebuildNumInfo(); + rebuildNumInfo(); } statusPickerVisible.value = false; }; -// 导航相关方法 -const navigateBack = () => { - uni.navigateBack(); -}; - -const toRollCallRecord = () => { - uni.navigateTo({ - url: "/pages/base/groupTeaching/rollCallRecord", - }); -}; - // 联系家长 -const contactParent = (student: any) => { +const contactParent = (dmXs: any) => { // 构建完整的学生信息,确保包含所有必要字段 const completeStudent = { - ...student, + ...dmXs, // 确保字段名的一致性 - id: student.xsId || student.id, - xsxm: student.xsxm || student.xm, - xstx: student.xstx || student.avatar, - xb: student.xb || student.gender, - sfzh: student.sfzh, - cstime: student.cstime, - njmc: student.njmcName || student.njmc, - bjmc: student.bjmc, + id: dmXs.xsId || dmXs.id, + xsxm: dmXs.xsXm || dmXs.xsxm || dmXs.xm, + xstx: dmXs.tx || dmXs.xstx || dmXs.avatar, + xb: dmXs.xb || dmXs.gender, + sfzh: dmXs.sfzh, + cstime: dmXs.cstime, + njmc: dmXs.njmcName || dmXs.njmc, + bjmc: dmXs.bjmc, // 如果后端返回的是njId和bjId,也保留 - njId: student.njId, - bjId: student.bjId + njId: dmXs.njId, + bjId: dmXs.bjId }; // 设置完整的学生信息到store中,供详情页面使用 @@ -309,19 +363,97 @@ const contactParent = (student: any) => { }); }; -// 提交数据 +// 数据验证 +const validateData = () => { + if (!xkkc.value || !xkkc.value.id) { + uni.showToast({ + title: "课程信息不完整", + icon: "none" + }); + return false; + } + + if (!js.value || !js.value.id) { + uni.showToast({ + title: "教师信息不完整", + icon: "none" + }); + return false; + } + + if (xsList.value.length === 0) { + uni.showToast({ + title: "没有学生数据", + icon: "none" + }); + return false; + } + + return true; +}; + +// 提交数据 - 匹配后端XkDm和XkDmXs实体 const submit = async () => { if (isSubmitting.value) { return; } + + // 数据验证 + if (!validateData()) { + return; + } + isSubmitting.value = true; + try { - const res = await jsdXkdmListApi({ - jsId: js.value.id, - xkkcId: xkkc.value.id, - dmtime: now, - xkdmList: xsList.value - }); + // 先上传媒体文件 + let photoUrls = ''; + let videoUrls = ''; + + if (mediaData.value.photoList.length > 0 || mediaData.value.videoList.length > 0) { + try { + if (dmPsRef.value) { + const uploadResult = await dmPsRef.value.uploadMedia(); + photoUrls = uploadResult.photoUrls; + videoUrls = uploadResult.videoUrls; + } + } catch (uploadError) { + console.error('媒体文件上传失败:', uploadError); + uni.showToast({ + title: '媒体文件上传失败,请重试', + icon: 'none' + }); + isSubmitting.value = false; + return; + } + } + + // 构建点名数据 - 匹配XkDm实体 + const dmData = { + ...dmInfo.value, + // 媒体文件地址 + zp: photoUrls, // 照片字段,逗号分隔的字符串 + sp: videoUrls, // 视频字段,逗号分隔的字符串 + // 学生列表 - 匹配XkDmXs实体 + xkDmXsList: xsList.value.map((xs: any) => ({ + xsId: xs.xsId || xs.id, // 学生ID + xsZt: xs.xsZt || xs.xszt, // 学生状态 + qdId: xs.qdId, // 关联选课清单ID + xsXm: xs.xsXm || xs.xsxm, // 学生姓名 + tx: xs.tx || xs.xstx, // 头像 + status: "A" // 状态 + })) + }; + dmData.jsId = js.value.id; // 教师ID + dmData.xkId = xkkc.value.xkId; // 选课ID + dmData.xkkcId = xkkc.value.id; // 选课课程ID + dmData.xkMc = xkkc.value.xkMc || xkkc.value.xkmc; // 选课名称 + dmData.xkkcMc = xkkc.value.kcmc; // 选课课程名称 + dmData.status = "A"; // 状态 + + console.log("提交的点名数据:", dmData); + + const res:any = await submitXkDmApi(dmData); if (res && res.resultCode === 1) { uni.showToast({ @@ -338,7 +470,7 @@ const submit = async () => { }, 1500); } else { uni.showToast({ - title: res?.resultMessage || "提交失败", + title: res.resultMessage || "提交失败", icon: "none", }); } @@ -355,8 +487,10 @@ const submit = async () => { // 页面加载时获取状态选项 onMounted(async () => { + console.log("页面加载,课程信息:", xkkc.value); await loadXsList(); await loadStatusOptions(); + console.log("学生列表加载完成,共", xsList.value.length, "人"); }); diff --git a/src/pages/base/groupTeaching/dmXkList.vue b/src/pages/view/routine/xk/dmIndex.vue similarity index 89% rename from src/pages/base/groupTeaching/dmXkList.vue rename to src/pages/view/routine/xk/dmIndex.vue index faa7fee..6b8c084 100644 --- a/src/pages/base/groupTeaching/dmXkList.vue +++ b/src/pages/view/routine/xk/dmIndex.vue @@ -40,7 +40,6 @@ 点名 点名记录 - @@ -65,7 +64,7 @@ import { } from "vue"; import { useUserStore } from "@/store/modules/user"; import { useDataStore } from "@/store/modules/data"; -import { getCurrentSemesterTeacherCoursesApi } from "@/api/base/server"; +import { xkkcListByJsIdApi } from "@/api/base/xkApi"; import { dmBeforeMinuteApi } from "@/api/system/config/index"; import dayjs from "dayjs"; @@ -100,15 +99,15 @@ onMounted(async () => { // 加载课程列表 const loadCourseList = async () => { try { - const res = await getCurrentSemesterTeacherCoursesApi(getJs.id); - if (res.resultCode == 1) { - if (res.result && res.result.length) { - xkkcList.value = res.result; - // 处理课程周期显示 - processCoursePeriods(); - } else { - xkkcList.value = []; - } + const res:any = await xkkcListByJsIdApi({ jsId: getJs.id }); + if (res.resultCode == 1) { + if (res.result && res.result.length) { + xkkcList.value = res.result; + // 处理课程周期显示 + processCoursePeriods(); + } else { + xkkcList.value = []; + } } else { xkkcList.value = []; uni.showToast({ @@ -244,7 +243,7 @@ const goDm = (xkkc: any) => { if (dmFlag) { setData(xkkc); uni.navigateTo({ - url: `/pages/base/groupTeaching/dmXkkcDetail`, + url: `/pages/view/routine/xk/dm`, }); } else { if (msg === "") { @@ -299,7 +298,7 @@ const goRecord = (xkkc: any) => { if (recordFlag) { setData(xkkc); uni.navigateTo({ - url: `/pages/base/groupTeaching/dmXkkcRecord`, + url: `/pages/view/routine/xk/dmXkkcRecord`, }); } else { if (msg === "") { @@ -313,61 +312,6 @@ const goRecord = (xkkc: any) => { } }; -// 跳转到课堂随拍 -const goPhoto = (xkkc: any) => { - const now = dayjs(); - let wDay = now.day(); - if (wDay === 0) { - wDay = 7; - } - let mDay = now.date(); - const strDate = now.format('YYYY-MM-DD') + ' '; - let photoFlag = false; - let msg = ""; - // 判断周期 - switch (xkkc.skzqlx) { - case '每天': - photoFlag = true; - break; - case '每周': - const daysOfWeek = xkkc.skzq.split(',').map(Number); - photoFlag = daysOfWeek.includes(wDay); - // 从wdNameList读取daysOfWeek对应的周几 - xkkc.skzqmc = daysOfWeek.map((day: number) => wdNameList[day - 1]).join(','); - break; - case '每月': - const daysOfMonth = xkkc.skzq.split(',').map(Number); - photoFlag = daysOfMonth.includes(mDay); - // 从根据编号加 - xkkc.skzqmc = daysOfMonth.map((day: number) => day + "号").join(','); - break; - } - // 判断日期是否合格 - if (photoFlag) { - // xkkc.skkstime开始时间向前dmBeforeMinute分钟 - const startTime = dayjs(strDate + xkkc.skkstime).subtract(dmBeforeMinute.value, 'minute').format('YYYY-MM-DD HH:mm:ss'); - const endTime = dayjs(strDate + xkkc.skjstime, 'YYYY-MM-DD HH:mm:ss'); - photoFlag = now.isBefore(endTime) && now.isAfter(startTime) - } else { - msg = "上课时间未到,无法随拍"; - } - if (photoFlag) { - setData(xkkc); - uni.navigateTo({ - url: `/pages/base/groupTeaching/photoXkkcDetail`, - }); - } else { - if (msg === "") { - msg = "上课时间未到,无法随拍"; - } - uni.showToast({ - title: msg, - icon: 'none', - duration: 2000 - }); - } -}; - // 页面卸载前清除定时器 onBeforeUnmount(() => { }); diff --git a/src/pages/view/routine/xk/dmList.vue b/src/pages/view/routine/xk/dmList.vue new file mode 100644 index 0000000..ae91cb7 --- /dev/null +++ b/src/pages/view/routine/xk/dmList.vue @@ -0,0 +1,384 @@ + + + + + diff --git a/src/pages/base/groupTeaching/dmXkkcRecord.vue b/src/pages/view/routine/xk/dmXsList.vue similarity index 50% rename from src/pages/base/groupTeaching/dmXkkcRecord.vue rename to src/pages/view/routine/xk/dmXsList.vue index 25b710d..c9bba10 100644 --- a/src/pages/base/groupTeaching/dmXkkcRecord.vue +++ b/src/pages/view/routine/xk/dmXsList.vue @@ -1,73 +1,93 @@