diff --git a/doc/副本课程协议—月卡篮球(1).docx b/doc/副本课程协议—月卡篮球(1).docx index 6d1ee5bb..525d3291 100644 Binary files a/doc/副本课程协议—月卡篮球(1).docx and b/doc/副本课程协议—月卡篮球(1).docx differ diff --git a/uniapp/common/config.js b/uniapp/common/config.js index c98a2253..2459f37b 100644 --- a/uniapp/common/config.js +++ b/uniapp/common/config.js @@ -1,6 +1,6 @@ // 环境变量配置 -const env = 'development' -// const env = 'prod' +// const env = 'development' +const env = 'prod' const isMockEnabled = false // 默认禁用Mock优先模式,仅作为回退 const isDebug = false // 默认启用调试模式 const devurl = 'http://localhost:20080/api' diff --git a/uniapp/pages-coach/coach/schedule/schedule_table.vue b/uniapp/pages-coach/coach/schedule/schedule_table.vue index f4662dbd..5aaac5a6 100644 --- a/uniapp/pages-coach/coach/schedule/schedule_table.vue +++ b/uniapp/pages-coach/coach/schedule/schedule_table.vue @@ -56,111 +56,45 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ date.weekName }} - {{ date.dateStr }} - 共{{ date.courseCount }}节课 - + + + + + + + + + + + - - + + + {{ date.weekName }} + {{ date.dateStr }} + 共{{ date.courseCount }}节课 + + + + + - - - + + @@ -554,11 +511,10 @@ export default { selectedClasses: [], // 滚动相关 - scrollTop: 0, scrollTimer: null, // 滚动防抖定时器 - // 表格配置 - tableWidth: 1500, // 表格总宽度,确保7天都能显示 (7*180+120=1380rpx) + // 表格配置 - 统一滚动后不包含左侧列宽度 + tableWidth: 1260, // 7天内容宽度 (7*180=1260rpx),左侧120rpx在容器内部 // 时间段配置(动态生成,支持场地时间限制) timeSlots: [], @@ -591,6 +547,12 @@ export default { selectedScheduleId: null, showScheduleDetail: false, + // 平台识别 + isH5: false, + + // 滚动视图高度 + scrollViewHeight: 0, + // 筛选参数 filterParams: { start_date: '', @@ -644,12 +606,20 @@ export default { }, mounted() { + // 检测平台 + // #ifdef H5 + this.isH5 = true + // #endif + this.initCurrentWeek() this.initTimeSlots() // 初始化响应式布局 this.handleResize() + // 计算scroll-view高度 + this.calculateScrollViewHeight() + // 先加载筛选选项,然后加载课程安排列表 this.loadFilterOptions().then(() => { this.loadScheduleList() @@ -679,6 +649,72 @@ export default { }, methods: { + // 时间标准化函数 - 统一时间格式为 HH:mm + normalizeTime(timeStr) { + if (!timeStr) return ''; + + // 移除空格并转换为字符串 + const cleanTime = String(timeStr).trim(); + + // 如果已经是 HH:mm 格式,直接返回 + if (/^\d{2}:\d{2}$/.test(cleanTime)) { + return cleanTime; + } + + // 如果是 H:mm 格式,补零 + if (/^\d{1}:\d{2}$/.test(cleanTime)) { + return '0' + cleanTime; + } + + // 如果是 HH:m 格式,补零 + if (/^\d{2}:\d{1}$/.test(cleanTime)) { + return cleanTime.slice(0, 3) + '0' + cleanTime.slice(3); + } + + // 如果是 H:m 格式,都补零 + if (/^\d{1}:\d{1}$/.test(cleanTime)) { + const [hour, minute] = cleanTime.split(':'); + return `0${hour}:0${minute}`; + } + + // 其他格式尝试解析 + try { + const [hour, minute] = cleanTime.split(':'); + const h = parseInt(hour).toString().padStart(2, '0'); + const m = parseInt(minute || 0).toString().padStart(2, '0'); + return `${h}:${m}`; + } catch (e) { + console.warn('时间格式解析失败:', timeStr); + return cleanTime; + } + }, + + // 时间匹配函数 - 支持时间段内匹配 + isTimeInSlot(courseTime, slotTime) { + const normalizedCourseTime = this.normalizeTime(courseTime); + const normalizedSlotTime = this.normalizeTime(slotTime); + + if (!normalizedCourseTime || !normalizedSlotTime) return false; + + // 精确匹配 + if (normalizedCourseTime === normalizedSlotTime) return true; + + // 查找时间段匹配 - 课程开始时间在这个时间段内 + const slotIndex = this.timeSlots.findIndex(slot => + this.normalizeTime(slot.time) === normalizedSlotTime + ); + + if (slotIndex >= 0 && slotIndex < this.timeSlots.length - 1) { + const currentSlotTime = this.normalizeTime(this.timeSlots[slotIndex].time); + const nextSlotTime = this.normalizeTime(this.timeSlots[slotIndex + 1].time); + + // 检查课程时间是否在当前时间段内(包含开始时间,不包含结束时间) + return normalizedCourseTime >= currentSlotTime && normalizedCourseTime < nextSlotTime; + } + + return false; + }, + // 初始化当前周 initCurrentWeek() { const today = new Date() @@ -857,8 +893,8 @@ export default { getCoursesByTimeAndDate(time, date) { const matchedCourses = this.courses.filter(course => { if (course.date !== date) return false - // 只在课程开始时间显示课程,不在后续时间段重复显示 - return course.time === time + // 使用改进的时间匹配算法 + return this.isTimeInSlot(course.time, time) }) return matchedCourses }, @@ -933,8 +969,7 @@ export default { this.filterParams.venue_id = ''; this.filterParams.class_id = ''; - // 如果切换了模式,重置滚动位置 - this.scrollTop = 0; + // 模式切换完成 // 切换模式后重新加载课程安排 this.loadScheduleList(); @@ -956,9 +991,8 @@ export default { this.applyFilters() this.closeFilterModal() - // 重新加载数据后,重置滚动位置 + // 重新加载数据 await this.loadScheduleList() - this.scrollTop = 0 // 如果筛选改变了时间段,需要重新生成时间列 if (this.selectedTimeRange !== '' || this.selectedVenueId !== null) { @@ -1146,34 +1180,35 @@ export default { if (res.code === 1) { // 转换数据格式 - this.courses = res.data.list.map(item => ({ - id: item.id, - date: item.course_date, - time: item.time_info?.start_time || item.time_slot?.split('-')[0], - courseName: item.course_name || '未命名课程', - students: `已报名${item.enrolled_count || 0}人`, - teacher: item.coach_name || '待分配', - teacher_id: item.coach_id, // 保存教练ID - status: item.status_text || '待定', - type: this.getCourseType(item), - venue: item.venue_name || '待分配', - venue_id: item.venue_id, // 保存场地ID - campus_name: item.campus_name || '', // 添加校区名称 - class_id: item.class_id, // 保存班级ID - class_name: item.class_name || '', // 添加班级名称 - duration: item.time_info?.duration || 60, - time_slot: item.time_slot, - raw: item, // 保存原始数据 - })) + this.courses = res.data.list.map(item => { + // 提取并标准化时间 + const rawTime = item.time_info?.start_time || item.time_slot?.split('-')[0] || ''; + const normalizedTime = this.normalizeTime(rawTime); + + return { + id: item.id, + date: item.course_date, + time: normalizedTime, // 使用标准化后的时间 + courseName: item.course_name || '未命名课程', + students: `已报名${item.enrolled_count || 0}人`, + teacher: item.coach_name || '待分配', + teacher_id: item.coach_id, // 保存教练ID + status: item.status_text || '待定', + type: this.getCourseType(item), + venue: item.venue_name || '待分配', + venue_id: item.venue_id, // 保存场地ID + campus_name: item.campus_name || '', // 添加校区名称 + class_id: item.class_id, // 保存班级ID + class_name: item.class_name || '', // 添加班级名称 + duration: item.time_info?.duration || 60, + time_slot: item.time_slot, + raw: item, // 保存原始数据 + } + }) // 根据当前视图模式动态更新左侧列数据 this.updateLeftColumnData(); - // 同步左右高度 - this.$nextTick(() => { - this.syncRowHeights(); - }); - } else { uni.showToast({ title: res.msg || '加载课程安排列表失败', @@ -1266,61 +1301,18 @@ export default { }, - // 同步左右行高度 - syncRowHeights() { - this.$nextTick(() => { - try { - let itemCount = 0; - - // 根据当前筛选模式确定行数 - if (this.activeFilter === 'time' || this.activeFilter === '') { - itemCount = this.timeSlots.length; - } else if (this.activeFilter === 'teacher') { - itemCount = this.teacherOptions.length; - } else if (this.activeFilter === 'classroom') { - itemCount = this.venues.length; - } else if (this.activeFilter === 'class') { - itemCount = this.classOptions.length; - } - - // 同步每一行的高度 - for (let i = 0; i < itemCount; i++) { - const scheduleRow = this.$refs[`scheduleRow_${i}`]; - const frozenCell = this.$refs[`frozenCell_${i}`]; - - if (scheduleRow && scheduleRow[0] && frozenCell && frozenCell[0]) { - // 获取右侧行的实际高度 - const rightRowHeight = scheduleRow[0].$el ? - scheduleRow[0].$el.offsetHeight : - scheduleRow[0].offsetHeight; - - // 设置左侧冻结单元格的高度 - if (frozenCell[0].$el) { - frozenCell[0].$el.style.minHeight = rightRowHeight + 'px'; - } else if (frozenCell[0].style) { - frozenCell[0].style.minHeight = rightRowHeight + 'px'; - } - } - } - } catch (error) { - console.warn('高度同步失败:', error); - } - }); - }, - - // 滚动事件处理函数 - 优化垂直滚动同步 + // 滚动事件处理函数 - 简化版本 onScroll(e) { - // 使用防抖优化滚动同步性能 + // 统一滚动区域后不需要同步滚动位置 + // 只保留防抖逻辑以优化性能 if (this.scrollTimer) { clearTimeout(this.scrollTimer) } this.scrollTimer = setTimeout(() => { - // 只需要同步垂直滚动位置给左侧时间列 - if (e.detail.scrollTop !== undefined && e.detail.scrollTop !== this.scrollTop) { - this.scrollTop = e.detail.scrollTop - } - }, 16) // 约60fps的更新频率 + // 可以在这里添加其他滚动相关的处理逻辑 + // 例如懒加载、滚动到顶部/底部的处理等 + }, 16) }, // 单元格点击 @@ -1523,6 +1515,36 @@ export default { // 暂时只显示提示信息,具体API调用可以后续实现 }, + // 计算scroll-view高度 + calculateScrollViewHeight() { + uni.getSystemInfo({ + success: (res) => { + // 获取屏幕高度 + let screenHeight = res.screenHeight || res.windowHeight + + // 计算其他元素占用的高度 + // 筛选区域 + 日期导航 + 统计信息 + 状态栏等 + let otherHeight = 0 + + // #ifdef H5 + otherHeight = 200 // H5端大约200px + // #endif + + // #ifdef MP-WEIXIN + otherHeight = 160 // 小程序端大约160px (rpx转换) + // #endif + + // 设置scroll-view高度 + this.scrollViewHeight = screenHeight - otherHeight + + // 最小高度限制 + if (this.scrollViewHeight < 300) { + this.scrollViewHeight = 300 + } + } + }) + }, + // 响应式调整 handleResize() { // 根据窗口宽度调整表格尺寸 @@ -1545,12 +1567,15 @@ export default { // #endif if (width <= 375) { - this.tableWidth = 1220 // 7*160+100=1220rpx + this.tableWidth = 1120 // 7*160=1120rpx (小屏幕每列160rpx) } else if (width <= 768) { - this.tableWidth = 1400 + this.tableWidth = 1260 // 7*180=1260rpx } else { - this.tableWidth = 1500 + this.tableWidth = 1260 // 7*180=1260rpx } + + // 重新计算scroll-view高度 + this.calculateScrollViewHeight() }, // 初始化模拟数据 @@ -1666,24 +1691,32 @@ export default { .schedule-main { flex: 1; position: relative; - display: flex; overflow: hidden; + height: 0; /* 配合flex: 1 确保高度计算正确 */ } -// 左侧冻结列 -.frozen-column { - width: 120rpx; - position: relative; - z-index: 3; - background-color: #292929; +// 统一滚动区域内部容器 +.schedule-container-inner { display: flex; flex-direction: column; - box-shadow: 4rpx 0 8rpx rgba(0, 0, 0, 0.1); - flex-shrink: 0; + min-width: 1380rpx; /* 左列120rpx + 7天 * 180rpx = 1380rpx */ +} + +// 表头行 +.schedule-header-row { + display: flex; + background: #434544; + border-bottom: 2px solid #29d3b4; + position: sticky; + top: 0; + z-index: 10; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3); } +// 左上角标题单元格 .time-header-cell { width: 120rpx; + min-width: 120rpx; min-height: 120rpx; padding: 20rpx 10rpx; color: #29d3b4; @@ -1691,75 +1724,13 @@ export default { font-weight: 500; text-align: center; border-right: 1px solid #555; - border-bottom: 2px solid #29d3b4; background-color: #434544; display: flex; align-items: center; justify-content: center; word-wrap: break-word; overflow-wrap: break-word; -} - -.frozen-content-scroll { - flex: 1; - overflow: hidden; - /* 优化滚动性能 */ - -webkit-overflow-scrolling: touch; - scroll-behavior: auto; - overscroll-behavior: none; -} - -.frozen-content { - width: 100%; -} - -.frozen-cell { - width: 120rpx; - min-height: 120rpx; - padding: 20rpx 10rpx; - color: #999; - font-size: 24rpx; - text-align: center; - border-right: 1px solid #434544; - border-bottom: 1px solid #434544; - background: #3a3a3a; - display: flex; - align-items: center; - justify-content: center; - word-wrap: break-word; - overflow-wrap: break-word; - - &.time-unavailable { - background: #2a2a2a; - color: #555; - opacity: 0.5; - } -} - -// 右侧内容区域 -.schedule-content-area { - flex: 1; - display: flex; - flex-direction: column; - overflow: hidden; -} - -// 合并滚动区域内部容器 -.schedule-container-inner { - display: flex; - flex-direction: column; - min-width: 1260rpx; /* 7天 * 180rpx = 1260rpx */ -} - -.date-header-container { - display: flex; - background: #434544; - border-bottom: 2px solid #29d3b4; - position: sticky; - top: 0; - z-index: 10; - /* 确保完全覆盖下方内容 */ - box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3); + flex-shrink: 0; } .date-header-cell { @@ -1796,16 +1767,23 @@ export default { // 内容滚动区域 .schedule-scroll { flex: 1; - overflow: scroll; + height: 100%; + width: 100%; + min-height: 0; /* 关键:允许flex子项收缩 */ /* 优化滚动性能 */ -webkit-overflow-scrolling: touch; scroll-behavior: auto; overscroll-behavior: none; + + /* 小程序端专用优化 */ + // #ifdef MP-WEIXIN + scroll-behavior: smooth; + // #endif } .schedule-grid { width: 100%; - min-width: 1260rpx; /* 7天 * 180rpx = 1260rpx */ + min-width: 1380rpx; /* 左列120rpx + 7天 * 180rpx = 1380rpx */ } // 行布局 @@ -1815,6 +1793,32 @@ export default { border-bottom: 1px solid #434544; } +// 左侧列单元格 (时间/教练/教室/班级) +.left-column-cell { + width: 120rpx; + min-width: 120rpx; + min-height: 120rpx; + padding: 20rpx 10rpx; + color: #999; + font-size: 24rpx; + text-align: center; + border-right: 1px solid #434544; + border-bottom: 1px solid #434544; + background: #3a3a3a; + display: flex; + align-items: center; + justify-content: center; + word-wrap: break-word; + overflow-wrap: break-word; + flex-shrink: 0; + + &.time-unavailable { + background: #2a2a2a; + color: #555; + opacity: 0.5; + } +} + .course-cell { width: 180rpx; min-width: 180rpx; @@ -1849,12 +1853,15 @@ export default { // 响应式适配 @media screen and (max-width: 375px) { - .time-column-fixed { + .left-column-cell { width: 100rpx; + min-width: 100rpx; + font-size: 22rpx; } - .time-header-cell, .time-cell { + .time-header-cell { width: 100rpx; + min-width: 100rpx; font-size: 22rpx; } diff --git a/uniapp/pages-market/clue/add_clues.vue b/uniapp/pages-market/clue/add_clues.vue index 7f99c75b..dce862aa 100644 --- a/uniapp/pages-market/clue/add_clues.vue +++ b/uniapp/pages-market/clue/add_clues.vue @@ -353,7 +353,7 @@ + @click="openDate(`promised_visit_time`)"> {{ (formData.promised_visit_time) ? formData.promised_visit_time : '点击选择' }} { - this.datetime_picker_show = true + this.date_picker_show = true }) }, //选择跟进时间 diff --git a/uniapp/pages/common/home/index.vue b/uniapp/pages/common/home/index.vue index a65f0292..e66df791 100644 --- a/uniapp/pages/common/home/index.vue +++ b/uniapp/pages/common/home/index.vue @@ -106,12 +106,12 @@ path: '/pages/common/dashboard/webview', params: { type: 'campus_data' } }, - { - title: '报销管理', - icon: 'wallet-filled', - path: '/pages-market/reimbursement/list', - params: { type: 'reimbursement' } - } + // { + // title: '报销管理', + // icon: 'wallet-filled', + // path: '/pages-market/reimbursement/list', + // params: { type: 'reimbursement' } + // } ] } },