feat(checkin): 展示打卡点评列表
同步 Apifox 打卡动态字段说明,并在打卡页面展示 feedback_list 点评内容与评分。 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Showing
4 changed files
with
568 additions
and
326 deletions
| ... | @@ -23,10 +23,10 @@ VITE_PIN = | ... | @@ -23,10 +23,10 @@ VITE_PIN = |
| 23 | # 反向代理服务器地址 | 23 | # 反向代理服务器地址 |
| 24 | # VITE_PROXY_TARGET = https://oa.anxinchashi.com/ | 24 | # VITE_PROXY_TARGET = https://oa.anxinchashi.com/ |
| 25 | # VITE_PROXY_TARGET = http://behalo.onwall.cn/ | 25 | # VITE_PROXY_TARGET = http://behalo.onwall.cn/ |
| 26 | -# VITE_PROXY_TARGET = http://oa-dev.onwall.cn/ | 26 | +VITE_PROXY_TARGET = http://oa-dev.onwall.cn/ |
| 27 | # VITE_PROXY_TARGET = https://oa.behalo.cc/ | 27 | # VITE_PROXY_TARGET = https://oa.behalo.cc/ |
| 28 | # VITE_PROXY_TARGET = https://www.wxgzjs.cn/ | 28 | # VITE_PROXY_TARGET = https://www.wxgzjs.cn/ |
| 29 | -VITE_PROXY_TARGET = https://wxm.behalo.cc/ | 29 | +# VITE_PROXY_TARGET = https://wxm.behalo.cc/ |
| 30 | 30 | ||
| 31 | # PC端地址(对应 Vite 默认开发端口 5173) | 31 | # PC端地址(对应 Vite 默认开发端口 5173) |
| 32 | VITE_MOBILE_URL = http://localhost:5173/ | 32 | VITE_MOBILE_URL = http://localhost:5173/ | ... | ... |
| ... | @@ -31,7 +31,7 @@ const Api = { | ... | @@ -31,7 +31,7 @@ const Api = { |
| 31 | * @return: data: [{ id 大作业id, cover 作业封面, title 大作业名称, begin_date 开始时间, end_date 结束时间, task_type 任务类型 [checkin=签到 | upload=上传附件 | count=计数], is_gray 作业是否应该置灰, is_finish 作业在当前周期是否已经达标, checkin_subtask_id 签到小作业的ID }] | 31 | * @return: data: [{ id 大作业id, cover 作业封面, title 大作业名称, begin_date 开始时间, end_date 结束时间, task_type 任务类型 [checkin=签到 | upload=上传附件 | count=计数], is_gray 作业是否应该置灰, is_finish 作业在当前周期是否已经达标, checkin_subtask_id 签到小作业的ID }] |
| 32 | */ | 32 | */ |
| 33 | 33 | ||
| 34 | -export const getTaskListAPI = (params) => fn(fetch.get(Api.GET_TASK_LIST, params)) | 34 | +export const getTaskListAPI = params => fn(fetch.get(Api.GET_TASK_LIST, params)) |
| 35 | 35 | ||
| 36 | /** | 36 | /** |
| 37 | * @description: 大作业详情 | 37 | * @description: 大作业详情 |
| ... | @@ -47,7 +47,7 @@ export const getTaskListAPI = (params) => fn(fetch.get(Api.GET_TASK_LIST, param | ... | @@ -47,7 +47,7 @@ export const getTaskListAPI = (params) => fn(fetch.get(Api.GET_TASK_LIST, param |
| 47 | * subtask_list 小作业列表 [{id,title,cycle,frequency,attachment_type,begin_date,end_date,is_finish}] , | 47 | * subtask_list 小作业列表 [{id,title,cycle,frequency,attachment_type,begin_date,end_date,is_finish}] , |
| 48 | * } | 48 | * } |
| 49 | */ | 49 | */ |
| 50 | -export const getTaskDetailAPI = (params) => fn(fetch.get(Api.GET_TASK_DETAIL, params)) | 50 | +export const getTaskDetailAPI = params => fn(fetch.get(Api.GET_TASK_DETAIL, params)) |
| 51 | 51 | ||
| 52 | /** | 52 | /** |
| 53 | * @description: 小作业列表 | 53 | * @description: 小作业列表 |
| ... | @@ -67,14 +67,14 @@ export const getTaskDetailAPI = (params) => fn(fetch.get(Api.GET_TASK_DETAIL, p | ... | @@ -67,14 +67,14 @@ export const getTaskDetailAPI = (params) => fn(fetch.get(Api.GET_TASK_DETAIL, p |
| 67 | * field_list 动态表单字段列表 [{field_name,label,type}] | 67 | * field_list 动态表单字段列表 [{field_name,label,type}] |
| 68 | * }] | 68 | * }] |
| 69 | */ | 69 | */ |
| 70 | -export const getSubtaskListAPI = (params) => fn(fetch.get(Api.GET_SUBTASK_LIST, params)) | 70 | +export const getSubtaskListAPI = params => fn(fetch.get(Api.GET_SUBTASK_LIST, params)) |
| 71 | 71 | ||
| 72 | /** | 72 | /** |
| 73 | * @description: 签到打卡 | 73 | * @description: 签到打卡 |
| 74 | * @param subtask_id 小作业ID | 74 | * @param subtask_id 小作业ID |
| 75 | * @returns | 75 | * @returns |
| 76 | */ | 76 | */ |
| 77 | -export const checkinTaskAPI = (params) => fn(fetch.post(Api.TASK_CHECKIN, params)) | 77 | +export const checkinTaskAPI = params => fn(fetch.post(Api.TASK_CHECKIN, params)) |
| 78 | 78 | ||
| 79 | /** | 79 | /** |
| 80 | * @description: 新增上传打卡 | 80 | * @description: 新增上传打卡 |
| ... | @@ -86,27 +86,39 @@ export const checkinTaskAPI = (params) => fn(fetch.post(Api.TASK_CHECKIN, param | ... | @@ -86,27 +86,39 @@ export const checkinTaskAPI = (params) => fn(fetch.post(Api.TASK_CHECKIN, param |
| 86 | * @param gratitude_form_list 感恩表单数据 [{id,name,city,unit,其他信息字段}] | 86 | * @param gratitude_form_list 感恩表单数据 [{id,name,city,unit,其他信息字段}] |
| 87 | * @returns | 87 | * @returns |
| 88 | */ | 88 | */ |
| 89 | -export const addUploadTaskAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_ADD, params)) | 89 | +export const addUploadTaskAPI = params => fn(fetch.post(Api.TASK_UPLOAD_ADD, params)) |
| 90 | 90 | ||
| 91 | /** | 91 | /** |
| 92 | * @description: 获取打卡动态列表 | 92 | * @description: 获取打卡动态列表 |
| 93 | - * @param task_id 上传作业ID | 93 | + * @param task_id 大作业ID |
| 94 | * @param subtask_id 小作业ID | 94 | * @param subtask_id 小作业ID |
| 95 | * @param date 日期 | 95 | * @param date 日期 |
| 96 | * @param keyword 搜索 | 96 | * @param keyword 搜索 |
| 97 | * @param order_by_time asc=正序,desc=倒序。默认为倒序 | 97 | * @param order_by_time asc=正序,desc=倒序。默认为倒序 |
| 98 | * @param limit 条数 | 98 | * @param limit 条数 |
| 99 | - * @param offset 页码 | 99 | + * @param page 页码 |
| 100 | - * @returns data: [{id 打卡动态ID, status 审批状态 3=待审批,5=审批通过,7=审批不通过, created_by 打卡人ID, username 打卡人昵称 | 100 | + * @returns data.checkin_list: [{ |
| 101 | - * avatar 打卡人头像, created_time 打卡时间, created_time_desc 打卡时间描述, note 打卡内容, | 101 | + * id 打卡动态ID, |
| 102 | + * status 审批状态 3=待审批,5=审批通过,7=审批不通过, | ||
| 103 | + * created_by 打卡人ID, | ||
| 104 | + * username 打卡人昵称, | ||
| 105 | + * avatar 打卡人头像, | ||
| 106 | + * created_time 打卡时间, | ||
| 107 | + * created_time_desc 打卡时间的描述(如:2小时前,1天前), | ||
| 108 | + * note 打卡内容, | ||
| 102 | * files[{meta_id,name,value,extension,file_type[image=上传图片,video=视频,audio=音频]}] 附件列表, | 109 | * files[{meta_id,name,value,extension,file_type[image=上传图片,video=视频,audio=音频]}] 附件列表, |
| 103 | - * like_count 点赞数, is_my 是不是我的打卡, is_like 我是否已经点赞, is_makeup 是否补卡 | 110 | + * like_count 点赞数, |
| 104 | - * subtask_title 小作业标题 | 111 | + * is_my 是不是我的打卡, |
| 105 | - * gratitude_count 感恩次数 | 112 | + * is_like 我是否已经点赞, |
| 106 | - * gratitude_form_list 感恩对象列表 [{id,name,city,unit}] | 113 | + * is_makeup 是否补卡, |
| 114 | + * subtask_id 小作业ID, | ||
| 115 | + * subtask_title 小作业标题, | ||
| 116 | + * gratitude_count 感恩次数, | ||
| 117 | + * gratitude_form_list 感恩表单数据 [{id,name,city,unit,其他信息字段}], | ||
| 118 | + * feedback_list 点评列表 [{id,note 点评内容,score 点评分数,created_time 点评时间,created_by 助教ID,created_avatar 助教头像,created_name 助教昵称}] | ||
| 107 | * }] | 119 | * }] |
| 108 | */ | 120 | */ |
| 109 | -export const getUploadTaskListAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_LIST, params)) | 121 | +export const getUploadTaskListAPI = params => fn(fetch.post(Api.TASK_UPLOAD_LIST, params)) |
| 110 | 122 | ||
| 111 | /** | 123 | /** |
| 112 | * @description: 上传打卡详情 | 124 | * @description: 上传打卡详情 |
| ... | @@ -119,7 +131,7 @@ export const getUploadTaskListAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_L | ... | @@ -119,7 +131,7 @@ export const getUploadTaskListAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_L |
| 119 | * gratitude_form_list 感恩表单数据 [{id,name,city,unit,其他信息字段}] | 131 | * gratitude_form_list 感恩表单数据 [{id,name,city,unit,其他信息字段}] |
| 120 | * } | 132 | * } |
| 121 | */ | 133 | */ |
| 122 | -export const getUploadTaskInfoAPI = (params) => fn(fetch.get(Api.TASK_UPLOAD_INFO, params)) | 134 | +export const getUploadTaskInfoAPI = params => fn(fetch.get(Api.TASK_UPLOAD_INFO, params)) |
| 123 | 135 | ||
| 124 | /** | 136 | /** |
| 125 | * @description: 编辑打卡动态 | 137 | * @description: 编辑打卡动态 |
| ... | @@ -130,28 +142,28 @@ export const getUploadTaskInfoAPI = (params) => fn(fetch.get(Api.TASK_UPLOAD_IN | ... | @@ -130,28 +142,28 @@ export const getUploadTaskInfoAPI = (params) => fn(fetch.get(Api.TASK_UPLOAD_IN |
| 130 | * @param gratitude_form_list 感恩表单数据 [{id,name,city,unit,其他信息字段}] | 142 | * @param gratitude_form_list 感恩表单数据 [{id,name,city,unit,其他信息字段}] |
| 131 | * @returns | 143 | * @returns |
| 132 | */ | 144 | */ |
| 133 | -export const editUploadTaskInfoAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_EDIT, params)) | 145 | +export const editUploadTaskInfoAPI = params => fn(fetch.post(Api.TASK_UPLOAD_EDIT, params)) |
| 134 | 146 | ||
| 135 | /** | 147 | /** |
| 136 | * @description: 删除打卡动态详情 | 148 | * @description: 删除打卡动态详情 |
| 137 | * @param i 打卡动态ID | 149 | * @param i 打卡动态ID |
| 138 | * @returns | 150 | * @returns |
| 139 | */ | 151 | */ |
| 140 | -export const delUploadTaskInfoAPI = (params) => fn(fetch.get(Api.TASK_UPLOAD_DEL, params)) | 152 | +export const delUploadTaskInfoAPI = params => fn(fetch.get(Api.TASK_UPLOAD_DEL, params)) |
| 141 | 153 | ||
| 142 | /** | 154 | /** |
| 143 | * @description: 给打卡点赞 | 155 | * @description: 给打卡点赞 |
| 144 | * @param checkin_id 打卡动态ID | 156 | * @param checkin_id 打卡动态ID |
| 145 | * @returns | 157 | * @returns |
| 146 | */ | 158 | */ |
| 147 | -export const likeUploadTaskInfoAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_LIKE, params)) | 159 | +export const likeUploadTaskInfoAPI = params => fn(fetch.post(Api.TASK_UPLOAD_LIKE, params)) |
| 148 | 160 | ||
| 149 | /** | 161 | /** |
| 150 | * @description: 取消点赞 | 162 | * @description: 取消点赞 |
| 151 | * @param checkin_id 打卡动态ID | 163 | * @param checkin_id 打卡动态ID |
| 152 | * @returns | 164 | * @returns |
| 153 | */ | 165 | */ |
| 154 | -export const dislikeUploadTaskInfoAPI = (params) => fn(fetch.post(Api.TASK_UPLOAD_DISLIKE, params)) | 166 | +export const dislikeUploadTaskInfoAPI = params => fn(fetch.post(Api.TASK_UPLOAD_DISLIKE, params)) |
| 155 | 167 | ||
| 156 | /** | 168 | /** |
| 157 | * @description: 老师查看打卡动态列表 | 169 | * @description: 老师查看打卡动态列表 |
| ... | @@ -182,7 +194,7 @@ export const dislikeUploadTaskInfoAPI = (params) => fn(fetch.post(Api.TASK_UPLO | ... | @@ -182,7 +194,7 @@ export const dislikeUploadTaskInfoAPI = (params) => fn(fetch.post(Api.TASK_UPLO |
| 182 | * }] | 194 | * }] |
| 183 | * } | 195 | * } |
| 184 | */ | 196 | */ |
| 185 | -export const getCheckinTeacherListAPI = (params) => fn(fetch.get(Api.CHECKIN_TEACHER_LIST, params)) | 197 | +export const getCheckinTeacherListAPI = params => fn(fetch.get(Api.CHECKIN_TEACHER_LIST, params)) |
| 186 | 198 | ||
| 187 | /** | 199 | /** |
| 188 | * @description: 老师审批打卡 | 200 | * @description: 老师审批打卡 |
| ... | @@ -190,7 +202,7 @@ export const getCheckinTeacherListAPI = (params) => fn(fetch.get(Api.CHECKIN_TE | ... | @@ -190,7 +202,7 @@ export const getCheckinTeacherListAPI = (params) => fn(fetch.get(Api.CHECKIN_TE |
| 190 | * @param status 审批状态 5=通过,7=拒绝 | 202 | * @param status 审批状态 5=通过,7=拒绝 |
| 191 | * @returns | 203 | * @returns |
| 192 | */ | 204 | */ |
| 193 | -export const checkinTaskReviewAPI = (params) => fn(fetch.post(Api.CHECKIN_TEACHER_REVIEW, params)) | 205 | +export const checkinTaskReviewAPI = params => fn(fetch.post(Api.CHECKIN_TEACHER_REVIEW, params)) |
| 194 | 206 | ||
| 195 | /** | 207 | /** |
| 196 | * @description: 老师查看已打卡日期 | 208 | * @description: 老师查看已打卡日期 |
| ... | @@ -203,11 +215,13 @@ export const checkinTaskReviewAPI = (params) => fn(fetch.post(Api.CHECKIN_TEACH | ... | @@ -203,11 +215,13 @@ export const checkinTaskReviewAPI = (params) => fn(fetch.post(Api.CHECKIN_TEACH |
| 203 | * @param keyword 搜索 | 215 | * @param keyword 搜索 |
| 204 | * @returns data: { my_checkin_dates 已打卡日期列表 } | 216 | * @returns data: { my_checkin_dates 已打卡日期列表 } |
| 205 | */ | 217 | */ |
| 206 | -export const getCheckinTeacherCheckedDatesAPI = (params) => fn(fetch.get(Api.CHECKIN_TEACHER_CHECKED_DATES, params)) | 218 | +export const getCheckinTeacherCheckedDatesAPI = params => |
| 219 | + fn(fetch.get(Api.CHECKIN_TEACHER_CHECKED_DATES, params)) | ||
| 207 | 220 | ||
| 208 | /** | 221 | /** |
| 209 | * @description: 复用感恩表单数据 | 222 | * @description: 复用感恩表单数据 |
| 210 | * @param subtask_id 小作业ID | 223 | * @param subtask_id 小作业ID |
| 211 | * @returns data: { gratitude_form_list 感恩表单数据 [{id,name,city,unit,其他信息字段}], last_used_list 最近使用的表单数据 [{id,name,city,unit,其他信息字段}] } | 224 | * @returns data: { gratitude_form_list 感恩表单数据 [{id,name,city,unit,其他信息字段}], last_used_list 最近使用的表单数据 [{id,name,city,unit,其他信息字段}] } |
| 212 | */ | 225 | */ |
| 213 | -export const reuseGratitudeFormAPI = (params) => fn(fetch.post(Api.CHECKIN_TEACHER_REUSE_GRATITUDE_FORM, params)) | 226 | +export const reuseGratitudeFormAPI = params => |
| 227 | + fn(fetch.post(Api.CHECKIN_TEACHER_REUSE_GRATITUDE_FORM, params)) | ... | ... |
| ... | @@ -4,17 +4,26 @@ | ... | @@ -4,17 +4,26 @@ |
| 4 | <div class="post-header"> | 4 | <div class="post-header"> |
| 5 | <van-row> | 5 | <van-row> |
| 6 | <van-col span="4"> | 6 | <van-col span="4"> |
| 7 | - <van-image round width="2.5rem" height="2.5rem" | 7 | + <van-image |
| 8 | - :src="getOptimizedUrl(post.user.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg')" | 8 | + round |
| 9 | - fit="cover" /> | 9 | + width="2.5rem" |
| 10 | + height="2.5rem" | ||
| 11 | + :src=" | ||
| 12 | + getOptimizedUrl(post.user.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg') | ||
| 13 | + " | ||
| 14 | + fit="cover" | ||
| 15 | + /> | ||
| 10 | </van-col> | 16 | </van-col> |
| 11 | <van-col span="17"> | 17 | <van-col span="17"> |
| 12 | <div class="user-info"> | 18 | <div class="user-info"> |
| 13 | <div class="username"> | 19 | <div class="username"> |
| 14 | {{ post.user.name }} | 20 | {{ post.user.name }} |
| 15 | <!-- 补卡标记 --> | 21 | <!-- 补卡标记 --> |
| 16 | - <span v-if="post.user.is_makeup" | 22 | + <span |
| 17 | - class="MakeupTag inline-flex items-center ml-2 px-2 py-0.5 rounded-full text-xs font-medium bg-green-50 text-green-600 border border-green-300">补打卡</span> | 23 | + v-if="post.user.is_makeup" |
| 24 | + class="MakeupTag ml-2 inline-flex items-center rounded-full border border-green-300 bg-green-50 px-2 py-0.5 text-xs font-medium text-green-600" | ||
| 25 | + >补打卡</span | ||
| 26 | + > | ||
| 18 | <slot name="header-tags"></slot> | 27 | <slot name="header-tags"></slot> |
| 19 | </div> | 28 | </div> |
| 20 | <div class="post-time">{{ post.user.time }}</div> | 29 | <div class="post-time">{{ post.user.time }}</div> |
| ... | @@ -41,12 +50,44 @@ | ... | @@ -41,12 +50,44 @@ |
| 41 | <div ref="textRef" class="post-text" :class="{ 'line-clamp-5': !isExpanded }"> | 50 | <div ref="textRef" class="post-text" :class="{ 'line-clamp-5': !isExpanded }"> |
| 42 | {{ post.content }} | 51 | {{ post.content }} |
| 43 | </div> | 52 | </div> |
| 44 | - <div v-if="showExpandBtn" class="expand-btn text-blue-500 text-sm mt-1 cursor-pointer" | 53 | + <div |
| 45 | - @click.stop="toggleExpand"> | 54 | + v-if="showExpandBtn" |
| 55 | + class="expand-btn mt-1 cursor-pointer text-sm text-blue-500" | ||
| 56 | + @click.stop="toggleExpand" | ||
| 57 | + > | ||
| 46 | {{ isExpanded ? '收起' : '全文' }} | 58 | {{ isExpanded ? '收起' : '全文' }} |
| 47 | </div> | 59 | </div> |
| 48 | </div> | 60 | </div> |
| 49 | 61 | ||
| 62 | + <div v-if="hasFeedbackList" class="feedback-list mt-4 space-y-3"> | ||
| 63 | + <div v-for="feedback in post.feedback_list" :key="feedback.id" class="feedback-item"> | ||
| 64 | + <div | ||
| 65 | + v-if="feedback.time" | ||
| 66 | + class="feedback-time mb-2 flex items-center text-xs text-gray-500" | ||
| 67 | + > | ||
| 68 | + <van-icon name="calendar-o" size="14" color="#10b981" class="mr-1" /> | ||
| 69 | + <span>{{ feedback.time }}</span> | ||
| 70 | + </div> | ||
| 71 | + | ||
| 72 | + <div v-if="feedback.note" class="feedback-note mb-2 flex items-start"> | ||
| 73 | + <van-icon name="chat-o" size="14" color="#3b82f6" class="mr-2 mt-0.5" /> | ||
| 74 | + <div class="text-sm leading-6 text-gray-700">{{ feedback.note }}</div> | ||
| 75 | + </div> | ||
| 76 | + | ||
| 77 | + <div v-if="feedback.score > 0" class="feedback-score flex items-center"> | ||
| 78 | + <van-icon name="star-o" size="14" color="#f59e0b" class="mr-2" /> | ||
| 79 | + <span class="mr-2 text-xs text-gray-500">评分</span> | ||
| 80 | + <van-rate | ||
| 81 | + :model-value="feedback.score" | ||
| 82 | + :size="14" | ||
| 83 | + color="#ffd21e" | ||
| 84 | + void-color="#eee" | ||
| 85 | + readonly | ||
| 86 | + /> | ||
| 87 | + </div> | ||
| 88 | + </div> | ||
| 89 | + </div> | ||
| 90 | + | ||
| 50 | <!-- 媒体内容 --> | 91 | <!-- 媒体内容 --> |
| 51 | <div class="post-media mt-2"> | 92 | <div class="post-media mt-2"> |
| 52 | <!-- 多附件Tab模式 --> | 93 | <!-- 多附件Tab模式 --> |
| ... | @@ -59,9 +100,14 @@ | ... | @@ -59,9 +100,14 @@ |
| 59 | <template v-if="tab.name === 'image'"> | 100 | <template v-if="tab.name === 'image'"> |
| 60 | <div class="post-images"> | 101 | <div class="post-images"> |
| 61 | <div class="post-image-item" v-for="(image, index) in post.images" :key="index"> | 102 | <div class="post-image-item" v-for="(image, index) in post.images" :key="index"> |
| 62 | - <van-image width="100%" height="100%" fit="cover" | 103 | + <van-image |
| 63 | - :src="getOptimizedUrl(image)" radius="5" | 104 | + width="100%" |
| 64 | - @click="openImagePreview(index)" /> | 105 | + height="100%" |
| 106 | + fit="cover" | ||
| 107 | + :src="getOptimizedUrl(image)" | ||
| 108 | + radius="5" | ||
| 109 | + @click="openImagePreview(index)" | ||
| 110 | + /> | ||
| 65 | </div> | 111 | </div> |
| 66 | </div> | 112 | </div> |
| 67 | </template> | 113 | </template> |
| ... | @@ -70,34 +116,55 @@ | ... | @@ -70,34 +116,55 @@ |
| 70 | <template v-if="tab.name === 'video'"> | 116 | <template v-if="tab.name === 'video'"> |
| 71 | <div v-for="(v, idx) in post.videoList" :key="idx"> | 117 | <div v-for="(v, idx) in post.videoList" :key="idx"> |
| 72 | <!-- 封面图 --> | 118 | <!-- 封面图 --> |
| 73 | - <div v-if="v.video && !v.isPlaying" | ||
| 74 | - class="relative w-full rounded-lg overflow-hidden" | ||
| 75 | - style="aspect-ratio: 16/9; margin-bottom: 1rem;"> | ||
| 76 | - <img :src="getOptimizedUrl(v.videoCover || 'https://cdn.ipadbiz.cn/mlaj/images/cover_video_2.png')" | ||
| 77 | - :alt="post.content" class="w-full h-full object-contain" /> | ||
| 78 | - <div class="absolute inset-0 flex items-center justify-center cursor-pointer bg-black/20" | ||
| 79 | - @click="startPlay(v)"> | ||
| 80 | <div | 119 | <div |
| 81 | - class="w-16 h-16 rounded-full bg-black/50 flex items-center justify-center hover:bg-black/70 transition-colors"> | 120 | + v-if="v.video && !v.isPlaying" |
| 121 | + class="relative w-full overflow-hidden rounded-lg" | ||
| 122 | + style="aspect-ratio: 16/9; margin-bottom: 1rem" | ||
| 123 | + > | ||
| 124 | + <img | ||
| 125 | + :src=" | ||
| 126 | + getOptimizedUrl( | ||
| 127 | + v.videoCover || 'https://cdn.ipadbiz.cn/mlaj/images/cover_video_2.png' | ||
| 128 | + ) | ||
| 129 | + " | ||
| 130 | + :alt="post.content" | ||
| 131 | + class="h-full w-full object-contain" | ||
| 132 | + /> | ||
| 133 | + <div | ||
| 134 | + class="absolute inset-0 flex cursor-pointer items-center justify-center bg-black/20" | ||
| 135 | + @click="startPlay(v)" | ||
| 136 | + > | ||
| 137 | + <div | ||
| 138 | + class="flex h-16 w-16 items-center justify-center rounded-full bg-black/50 transition-colors hover:bg-black/70" | ||
| 139 | + > | ||
| 82 | <van-icon name="play-circle-o" class="text-white" size="40" /> | 140 | <van-icon name="play-circle-o" class="text-white" size="40" /> |
| 83 | </div> | 141 | </div> |
| 84 | </div> | 142 | </div> |
| 85 | </div> | 143 | </div> |
| 86 | <!-- 视频播放器 --> | 144 | <!-- 视频播放器 --> |
| 87 | - <VideoPlayer v-if="v.video && v.isPlaying" :video-url="v.video" | 145 | + <VideoPlayer |
| 146 | + v-if="v.video && v.isPlaying" | ||
| 147 | + :video-url="v.video" | ||
| 88 | :video-id="v.id || `video-${post.id}-${idx}`" | 148 | :video-id="v.id || `video-${post.id}-${idx}`" |
| 89 | - :use-native-on-ios="false" class="post-video rounded-lg overflow-hidden" | 149 | + :use-native-on-ios="false" |
| 90 | - :ref="(el) => setVideoRef(el, v.id)" | 150 | + class="post-video overflow-hidden rounded-lg" |
| 91 | - @onPlay="(player) => handleVideoPlay(player, v)" | 151 | + :ref="el => setVideoRef(el, v.id)" |
| 92 | - @onPause="handleVideoPause" /> | 152 | + @onPlay="player => handleVideoPlay(player, v)" |
| 153 | + @onPause="handleVideoPause" | ||
| 154 | + /> | ||
| 93 | </div> | 155 | </div> |
| 94 | </template> | 156 | </template> |
| 95 | 157 | ||
| 96 | <!-- 音频内容 --> | 158 | <!-- 音频内容 --> |
| 97 | <template v-if="tab.name === 'audio'"> | 159 | <template v-if="tab.name === 'audio'"> |
| 98 | - <AudioPlayer v-if="post.audio && post.audio.length" :songs="post.audio" | 160 | + <AudioPlayer |
| 99 | - class="post-audio" :id="post.id" :ref="(el) => setAudioRef(el, post.id)" | 161 | + v-if="post.audio && post.audio.length" |
| 100 | - @play="handleAudioPlay" /> | 162 | + :songs="post.audio" |
| 163 | + class="post-audio" | ||
| 164 | + :id="post.id" | ||
| 165 | + :ref="el => setAudioRef(el, post.id)" | ||
| 166 | + @play="handleAudioPlay" | ||
| 167 | + /> | ||
| 101 | </template> | 168 | </template> |
| 102 | </div> | 169 | </div> |
| 103 | </van-tab> | 170 | </van-tab> |
| ... | @@ -109,8 +176,14 @@ | ... | @@ -109,8 +176,14 @@ |
| 109 | <!-- 图片列表 --> | 176 | <!-- 图片列表 --> |
| 110 | <div v-if="post.images && post.images.length" class="post-images"> | 177 | <div v-if="post.images && post.images.length" class="post-images"> |
| 111 | <div class="post-image-item" v-for="(image, index) in post.images" :key="index"> | 178 | <div class="post-image-item" v-for="(image, index) in post.images" :key="index"> |
| 112 | - <van-image width="100%" height="100%" fit="cover" :src="getOptimizedUrl(image)" radius="5" | 179 | + <van-image |
| 113 | - @click="openImagePreview(index)" /> | 180 | + width="100%" |
| 181 | + height="100%" | ||
| 182 | + fit="cover" | ||
| 183 | + :src="getOptimizedUrl(image)" | ||
| 184 | + radius="5" | ||
| 185 | + @click="openImagePreview(index)" | ||
| 186 | + /> | ||
| 114 | </div> | 187 | </div> |
| 115 | </div> | 188 | </div> |
| 116 | 189 | ||
| ... | @@ -118,33 +191,63 @@ | ... | @@ -118,33 +191,63 @@ |
| 118 | <div v-if="post.videoList && post.videoList.length"> | 191 | <div v-if="post.videoList && post.videoList.length"> |
| 119 | <div v-for="(v, idx) in post.videoList" :key="idx"> | 192 | <div v-for="(v, idx) in post.videoList" :key="idx"> |
| 120 | <!-- 封面图 --> | 193 | <!-- 封面图 --> |
| 121 | - <div v-if="v.video && !v.isPlaying" class="relative w-full rounded-lg overflow-hidden" | ||
| 122 | - style="aspect-ratio: 16/9; margin-bottom: 1rem;"> | ||
| 123 | - <img :src="getOptimizedUrl(v.videoCover || 'https://cdn.ipadbiz.cn/mlaj/images/cover_video_2.png')" | ||
| 124 | - :alt="post.content" class="w-full h-full object-contain" /> | ||
| 125 | - <div class="absolute inset-0 flex items-center justify-center cursor-pointer bg-black/20" | ||
| 126 | - @click="startPlay(v)"> | ||
| 127 | <div | 194 | <div |
| 128 | - class="w-16 h-16 rounded-full bg-black/50 flex items-center justify-center hover:bg-black/70 transition-colors"> | 195 | + v-if="v.video && !v.isPlaying" |
| 196 | + class="relative w-full overflow-hidden rounded-lg" | ||
| 197 | + style="aspect-ratio: 16/9; margin-bottom: 1rem" | ||
| 198 | + > | ||
| 199 | + <img | ||
| 200 | + :src=" | ||
| 201 | + getOptimizedUrl( | ||
| 202 | + v.videoCover || 'https://cdn.ipadbiz.cn/mlaj/images/cover_video_2.png' | ||
| 203 | + ) | ||
| 204 | + " | ||
| 205 | + :alt="post.content" | ||
| 206 | + class="h-full w-full object-contain" | ||
| 207 | + /> | ||
| 208 | + <div | ||
| 209 | + class="absolute inset-0 flex cursor-pointer items-center justify-center bg-black/20" | ||
| 210 | + @click="startPlay(v)" | ||
| 211 | + > | ||
| 212 | + <div | ||
| 213 | + class="flex h-16 w-16 items-center justify-center rounded-full bg-black/50 transition-colors hover:bg-black/70" | ||
| 214 | + > | ||
| 129 | <van-icon name="play-circle-o" class="text-white" size="40" /> | 215 | <van-icon name="play-circle-o" class="text-white" size="40" /> |
| 130 | </div> | 216 | </div> |
| 131 | </div> | 217 | </div> |
| 132 | </div> | 218 | </div> |
| 133 | <!-- 视频播放器 --> | 219 | <!-- 视频播放器 --> |
| 134 | - <VideoPlayer v-if="v.video && v.isPlaying" :video-url="v.video" | 220 | + <VideoPlayer |
| 135 | - :video-id="v.id || `video-${post.id}-${idx}`" :use-native-on-ios="false" | 221 | + v-if="v.video && v.isPlaying" |
| 136 | - class="post-video rounded-lg overflow-hidden" :ref="(el) => setVideoRef(el, v.id)" | 222 | + :video-url="v.video" |
| 137 | - @onPlay="(player) => handleVideoPlay(player, v)" @onPause="handleVideoPause" /> | 223 | + :video-id="v.id || `video-${post.id}-${idx}`" |
| 224 | + :use-native-on-ios="false" | ||
| 225 | + class="post-video overflow-hidden rounded-lg" | ||
| 226 | + :ref="el => setVideoRef(el, v.id)" | ||
| 227 | + @onPlay="player => handleVideoPlay(player, v)" | ||
| 228 | + @onPause="handleVideoPause" | ||
| 229 | + /> | ||
| 138 | </div> | 230 | </div> |
| 139 | </div> | 231 | </div> |
| 140 | 232 | ||
| 141 | <!-- 音频播放器 --> | 233 | <!-- 音频播放器 --> |
| 142 | - <AudioPlayer v-if="post.audio && post.audio.length" :songs="post.audio" class="post-audio" | 234 | + <AudioPlayer |
| 143 | - :id="post.id" :ref="(el) => setAudioRef(el, post.id)" @play="handleAudioPlay" /> | 235 | + v-if="post.audio && post.audio.length" |
| 236 | + :songs="post.audio" | ||
| 237 | + class="post-audio" | ||
| 238 | + :id="post.id" | ||
| 239 | + :ref="el => setAudioRef(el, post.id)" | ||
| 240 | + @play="handleAudioPlay" | ||
| 241 | + /> | ||
| 144 | </div> | 242 | </div> |
| 145 | 243 | ||
| 146 | - <van-image-preview v-if="post.images && post.images.length" v-model:show="showLocalImagePreview" | 244 | + <van-image-preview |
| 147 | - :images="post.images" :start-position="localStartPosition" :show-index="true" /> | 245 | + v-if="post.images && post.images.length" |
| 246 | + v-model:show="showLocalImagePreview" | ||
| 247 | + :images="post.images" | ||
| 248 | + :start-position="localStartPosition" | ||
| 249 | + :show-index="true" | ||
| 250 | + /> | ||
| 148 | </div> | 251 | </div> |
| 149 | </div> | 252 | </div> |
| 150 | 253 | ||
| ... | @@ -152,8 +255,12 @@ | ... | @@ -152,8 +255,12 @@ |
| 152 | <div class="post-footer flex items-center justify-between"> | 255 | <div class="post-footer flex items-center justify-between"> |
| 153 | <!-- 左侧:点赞 --> | 256 | <!-- 左侧:点赞 --> |
| 154 | <div class="flex items-center"> | 257 | <div class="flex items-center"> |
| 155 | - <van-icon @click="emit('like', post)" name="good-job" class="like-icon" | 258 | + <van-icon |
| 156 | - :color="post.is_liked ? 'red' : ''" /> | 259 | + @click="emit('like', post)" |
| 260 | + name="good-job" | ||
| 261 | + class="like-icon" | ||
| 262 | + :color="post.is_liked ? 'red' : ''" | ||
| 263 | + /> | ||
| 157 | <span class="like-count ml-1">{{ post.likes }}</span> | 264 | <span class="like-count ml-1">{{ post.likes }}</span> |
| 158 | </div> | 265 | </div> |
| 159 | <!-- 右侧:自定义操作 --> | 266 | <!-- 右侧:自定义操作 --> |
| ... | @@ -195,9 +302,9 @@ | ... | @@ -195,9 +302,9 @@ |
| 195 | * const handleLike = (post) => console.log('like', post) | 302 | * const handleLike = (post) => console.log('like', post) |
| 196 | */ | 303 | */ |
| 197 | import { ref, computed, watchEffect, onMounted, nextTick, watch } from 'vue' | 304 | import { ref, computed, watchEffect, onMounted, nextTick, watch } from 'vue' |
| 198 | -import PostCountModel from "@/components/count/postCountModel.vue"; | 305 | +import PostCountModel from '@/components/count/postCountModel.vue' |
| 199 | -import VideoPlayer from "@/components/media/VideoPlayer.vue"; | 306 | +import VideoPlayer from '@/components/media/VideoPlayer.vue' |
| 200 | -import AudioPlayer from "@/components/media/AudioPlayer.vue"; | 307 | +import AudioPlayer from '@/components/media/AudioPlayer.vue' |
| 201 | 308 | ||
| 202 | const props = defineProps({ | 309 | const props = defineProps({ |
| 203 | /** | 310 | /** |
| ... | @@ -214,11 +321,15 @@ const props = defineProps({ | ... | @@ -214,11 +321,15 @@ const props = defineProps({ |
| 214 | */ | 321 | */ |
| 215 | post: { type: Object, required: true }, | 322 | post: { type: Object, required: true }, |
| 216 | /** 是否使用 CDN 优化链接 */ | 323 | /** 是否使用 CDN 优化链接 */ |
| 217 | - useCdnOptimization: { type: Boolean, default: false } | 324 | + useCdnOptimization: { type: Boolean, default: false }, |
| 218 | }) | 325 | }) |
| 219 | 326 | ||
| 220 | const emit = defineEmits(['like', 'edit', 'delete', 'video-play', 'audio-play']) | 327 | const emit = defineEmits(['like', 'edit', 'delete', 'video-play', 'audio-play']) |
| 221 | 328 | ||
| 329 | +const hasFeedbackList = computed( | ||
| 330 | + () => Array.isArray(props.post.feedback_list) && props.post.feedback_list.length > 0 | ||
| 331 | +) | ||
| 332 | + | ||
| 222 | // 文本折叠逻辑 | 333 | // 文本折叠逻辑 |
| 223 | const isExpanded = ref(false) | 334 | const isExpanded = ref(false) |
| 224 | const showExpandBtn = ref(false) | 335 | const showExpandBtn = ref(false) |
| ... | @@ -247,19 +358,23 @@ onMounted(() => { | ... | @@ -247,19 +358,23 @@ onMounted(() => { |
| 247 | }) | 358 | }) |
| 248 | }) | 359 | }) |
| 249 | 360 | ||
| 250 | -watch(() => props.post.content, () => { | 361 | +watch( |
| 362 | + () => props.post.content, | ||
| 363 | + () => { | ||
| 251 | isExpanded.value = false // Reset expansion on content change | 364 | isExpanded.value = false // Reset expansion on content change |
| 252 | nextTick(() => { | 365 | nextTick(() => { |
| 253 | checkTextOverflow() | 366 | checkTextOverflow() |
| 254 | }) | 367 | }) |
| 255 | -}) | 368 | + } |
| 369 | +) | ||
| 256 | 370 | ||
| 257 | // 多媒体Tab逻辑 | 371 | // 多媒体Tab逻辑 |
| 258 | const activeTab = ref('') | 372 | const activeTab = ref('') |
| 259 | const mediaTabs = computed(() => { | 373 | const mediaTabs = computed(() => { |
| 260 | const tabs = [] | 374 | const tabs = [] |
| 261 | if (props.post.images && props.post.images.length) tabs.push({ label: '图片', name: 'image' }) | 375 | if (props.post.images && props.post.images.length) tabs.push({ label: '图片', name: 'image' }) |
| 262 | - if (props.post.videoList && props.post.videoList.length) tabs.push({ label: '视频', name: 'video' }) | 376 | + if (props.post.videoList && props.post.videoList.length) |
| 377 | + tabs.push({ label: '视频', name: 'video' }) | ||
| 263 | if (props.post.audio && props.post.audio.length) tabs.push({ label: '音频', name: 'audio' }) | 378 | if (props.post.audio && props.post.audio.length) tabs.push({ label: '音频', name: 'audio' }) |
| 264 | return tabs | 379 | return tabs |
| 265 | }) | 380 | }) |
| ... | @@ -278,7 +393,7 @@ const localStartPosition = ref(0) | ... | @@ -278,7 +393,7 @@ const localStartPosition = ref(0) |
| 278 | * @description 打开图片预览 | 393 | * @description 打开图片预览 |
| 279 | * @param {number} index 图片索引 | 394 | * @param {number} index 图片索引 |
| 280 | */ | 395 | */ |
| 281 | -const openImagePreview = (index) => { | 396 | +const openImagePreview = index => { |
| 282 | localStartPosition.value = index | 397 | localStartPosition.value = index |
| 283 | showLocalImagePreview.value = true | 398 | showLocalImagePreview.value = true |
| 284 | } | 399 | } |
| ... | @@ -319,7 +434,7 @@ const setAudioRef = (el, id) => { | ... | @@ -319,7 +434,7 @@ const setAudioRef = (el, id) => { |
| 319 | * @param {string} url 原始链接 | 434 | * @param {string} url 原始链接 |
| 320 | * @returns {string} 优化后的链接 | 435 | * @returns {string} 优化后的链接 |
| 321 | */ | 436 | */ |
| 322 | -const getOptimizedUrl = (url) => { | 437 | +const getOptimizedUrl = url => { |
| 323 | if (!props.useCdnOptimization) return url | 438 | if (!props.useCdnOptimization) return url |
| 324 | if (url.includes('?')) return url | 439 | if (url.includes('?')) return url |
| 325 | return `${url}?imageMogr2/thumbnail/200x/strip/quality/70` | 440 | return `${url}?imageMogr2/thumbnail/200x/strip/quality/70` |
| ... | @@ -330,7 +445,7 @@ const getOptimizedUrl = (url) => { | ... | @@ -330,7 +445,7 @@ const getOptimizedUrl = (url) => { |
| 330 | * @description 开始播放视频 | 445 | * @description 开始播放视频 |
| 331 | * @param {Object} v 视频对象 | 446 | * @param {Object} v 视频对象 |
| 332 | */ | 447 | */ |
| 333 | -const startPlay = (v) => { | 448 | +const startPlay = v => { |
| 334 | // 暂停当前卡片中的其他视频 | 449 | // 暂停当前卡片中的其他视频 |
| 335 | props.post.videoList.forEach(item => { | 450 | props.post.videoList.forEach(item => { |
| 336 | if (item.id !== v.id) item.isPlaying = false | 451 | if (item.id !== v.id) item.isPlaying = false |
| ... | @@ -362,7 +477,7 @@ const handleVideoPause = () => { | ... | @@ -362,7 +477,7 @@ const handleVideoPause = () => { |
| 362 | * @description 处理音频播放事件 | 477 | * @description 处理音频播放事件 |
| 363 | * @param {Object} player 播放器实例 | 478 | * @param {Object} player 播放器实例 |
| 364 | */ | 479 | */ |
| 365 | -const handleAudioPlay = (player) => { | 480 | +const handleAudioPlay = player => { |
| 366 | // 停止本地视频 | 481 | // 停止本地视频 |
| 367 | stopLocalVideos() | 482 | stopLocalVideos() |
| 368 | // 通知父组件 | 483 | // 通知父组件 |
| ... | @@ -378,7 +493,7 @@ const stopLocalVideos = () => { | ... | @@ -378,7 +493,7 @@ const stopLocalVideos = () => { |
| 378 | player.pause() | 493 | player.pause() |
| 379 | } | 494 | } |
| 380 | }) | 495 | }) |
| 381 | - props.post.videoList.forEach(v => v.isPlaying = false) | 496 | + props.post.videoList.forEach(v => (v.isPlaying = false)) |
| 382 | } | 497 | } |
| 383 | 498 | ||
| 384 | /** | 499 | /** |
| ... | @@ -400,7 +515,7 @@ defineExpose({ | ... | @@ -400,7 +515,7 @@ defineExpose({ |
| 400 | stopLocalAudio() | 515 | stopLocalAudio() |
| 401 | }, | 516 | }, |
| 402 | // 暴露 post id 以便父组件查找 | 517 | // 暴露 post id 以便父组件查找 |
| 403 | - id: props.post.id | 518 | + id: props.post.id, |
| 404 | }) | 519 | }) |
| 405 | </script> | 520 | </script> |
| 406 | 521 | ||
| ... | @@ -458,6 +573,15 @@ defineExpose({ | ... | @@ -458,6 +573,15 @@ defineExpose({ |
| 458 | word-wrap: break-word; | 573 | word-wrap: break-word; |
| 459 | } | 574 | } |
| 460 | 575 | ||
| 576 | + .feedback-list { | ||
| 577 | + .feedback-item { | ||
| 578 | + padding: 0.75rem; | ||
| 579 | + border-radius: 0.75rem; | ||
| 580 | + background: #f8fafc; | ||
| 581 | + border: 1px solid #e5e7eb; | ||
| 582 | + } | ||
| 583 | + } | ||
| 584 | + | ||
| 461 | .post-media { | 585 | .post-media { |
| 462 | .post-images { | 586 | .post-images { |
| 463 | display: flex; | 587 | display: flex; | ... | ... |
| ... | @@ -10,10 +10,19 @@ | ... | @@ -10,10 +10,19 @@ |
| 10 | <van-config-provider :theme-vars="themeVars"> | 10 | <van-config-provider :theme-vars="themeVars"> |
| 11 | <!-- 固定的日历组件 --> | 11 | <!-- 固定的日历组件 --> |
| 12 | <div class="fixed-calendar" ref="fixedCalendarWrapper"> | 12 | <div class="fixed-calendar" ref="fixedCalendarWrapper"> |
| 13 | - <CollapsibleCalendar ref="calendarRef" :title="taskDetail.title" :formatter="formatter" | 13 | + <CollapsibleCalendar |
| 14 | - :subtask-list="taskDetail.subtask_list" :has-selected-date="hasUserSelectedDate" v-model="selectedDate" | 14 | + ref="calendarRef" |
| 15 | - @select="onSelectDay" @click-subtitle="onClickSubtitle" @panel-change="onPanelChange" | 15 | + :title="taskDetail.title" |
| 16 | - @select-course="onSelectCourse" @clear-date="onClearSelectedDate" /> | 16 | + :formatter="formatter" |
| 17 | + :subtask-list="taskDetail.subtask_list" | ||
| 18 | + :has-selected-date="hasUserSelectedDate" | ||
| 19 | + v-model="selectedDate" | ||
| 20 | + @select="onSelectDay" | ||
| 21 | + @click-subtitle="onClickSubtitle" | ||
| 22 | + @panel-change="onPanelChange" | ||
| 23 | + @select-course="onSelectCourse" | ||
| 24 | + @clear-date="onClearSelectedDate" | ||
| 25 | + /> | ||
| 17 | </div> | 26 | </div> |
| 18 | 27 | ||
| 19 | <!-- 可滚动的内容区域 --> | 28 | <!-- 可滚动的内容区域 --> |
| ... | @@ -27,26 +36,31 @@ | ... | @@ -27,26 +36,31 @@ |
| 27 | </div> | 36 | </div> |
| 28 | </div> | 37 | </div> |
| 29 | 38 | ||
| 30 | - <div v-if="taskDetail.task_type === 'count'" class="text-wrapper" style="padding-bottom: 0;"> | 39 | + <div v-if="taskDetail.task_type === 'count'" class="text-wrapper" style="padding-bottom: 0"> |
| 31 | <div class="text-header">我的{{ dynamicFieldText }}</div> | 40 | <div class="text-header">我的{{ dynamicFieldText }}</div> |
| 32 | - <div style="margin: 0.5rem 0;"> | 41 | + <div style="margin: 0.5rem 0"> |
| 33 | <div class="flex items-center"> | 42 | <div class="flex items-center"> |
| 34 | <!-- 今日次数 --> | 43 | <!-- 今日次数 --> |
| 35 | - <div class="flex-1 flex flex-col items-center border-r border-gray-100"> | 44 | + <div class="flex flex-1 flex-col items-center border-r border-gray-100"> |
| 36 | <div class="flex items-baseline"> | 45 | <div class="flex items-baseline"> |
| 37 | - <span class="text-3xl font-bold text-[#4caf50] leading-none">{{ myTodayGratitudeCount }}</span> | 46 | + <span class="text-3xl font-bold leading-none text-[#4caf50]">{{ |
| 38 | - <span class="text-xs text-gray-400 ml-1 transform translate-y-0.5">次</span> | 47 | + myTodayGratitudeCount |
| 48 | + }}</span> | ||
| 49 | + <span class="ml-1 translate-y-0.5 transform text-xs text-gray-400">次</span> | ||
| 39 | </div> | 50 | </div> |
| 40 | - <div class="text-xs text-gray-500 mt-2">今日次数</div> | 51 | + <div class="mt-2 text-xs text-gray-500">今日次数</div> |
| 41 | </div> | 52 | </div> |
| 42 | <!-- 累计次数 --> | 53 | <!-- 累计次数 --> |
| 43 | - <div class="flex-1 flex flex-col items-center"> | 54 | + <div class="flex flex-1 flex-col items-center"> |
| 44 | <div class="flex items-baseline"> | 55 | <div class="flex items-baseline"> |
| 45 | - <span class="text-3xl font-bold text-[#ff9800] leading-none" style="color: #ff9800;">{{ | 56 | + <span |
| 46 | - myTotalGratitudeCount }}</span> | 57 | + class="text-3xl font-bold leading-none text-[#ff9800]" |
| 47 | - <span class="text-xs text-gray-400 ml-1 transform translate-y-0.5">次</span> | 58 | + style="color: #ff9800" |
| 59 | + >{{ myTotalGratitudeCount }}</span | ||
| 60 | + > | ||
| 61 | + <span class="ml-1 translate-y-0.5 transform text-xs text-gray-400">次</span> | ||
| 48 | </div> | 62 | </div> |
| 49 | - <div class="text-xs text-gray-500 mt-2">累计次数</div> | 63 | + <div class="mt-2 text-xs text-gray-500">累计次数</div> |
| 50 | </div> | 64 | </div> |
| 51 | </div> | 65 | </div> |
| 52 | </div> | 66 | </div> |
| ... | @@ -54,17 +68,17 @@ | ... | @@ -54,17 +68,17 @@ |
| 54 | 68 | ||
| 55 | <div v-if="showProgress" class="text-wrapper"> | 69 | <div v-if="showProgress" class="text-wrapper"> |
| 56 | <div class="text-header">目标进度</div> | 70 | <div class="text-header">目标进度</div> |
| 57 | - <div style="background-color: #FFF; margin-top: 1rem;"> | 71 | + <div style="background-color: #fff; margin-top: 1rem"> |
| 58 | <div class="grade-percentage-main"> | 72 | <div class="grade-percentage-main"> |
| 59 | - <van-row justify="space-between" style="margin: 0.5rem 0; font-size: 0.9rem;"> | 73 | + <van-row justify="space-between" style="margin: 0.5rem 0; font-size: 0.9rem"> |
| 60 | <van-col span="12"> | 74 | <van-col span="12"> |
| 61 | <span>作业目标</span> | 75 | <span>作业目标</span> |
| 62 | </van-col> | 76 | </van-col> |
| 63 | - <van-col span="12" style="text-align: right;"> | 77 | + <van-col span="12" style="text-align: right"> |
| 64 | - <span style="font-weight: bold;">{{ progress1 }}%</span> | 78 | + <span style="font-weight: bold">{{ progress1 }}%</span> |
| 65 | </van-col> | 79 | </van-col> |
| 66 | </van-row> | 80 | </van-row> |
| 67 | - <div style="overflow: hidden;"> | 81 | + <div style="overflow: hidden"> |
| 68 | <van-progress :percentage="progress1" color="#4caf50" :show-pivot="false" /> | 82 | <van-progress :percentage="progress1" color="#4caf50" :show-pivot="false" /> |
| 69 | </div> | 83 | </div> |
| 70 | </div> | 84 | </div> |
| ... | @@ -79,32 +93,58 @@ | ... | @@ -79,32 +93,58 @@ |
| 79 | </van-row> | 93 | </van-row> |
| 80 | <van-progress :percentage="progress2" color="#4caf50" :show-pivot="false" /> | 94 | <van-progress :percentage="progress2" color="#4caf50" :show-pivot="false" /> |
| 81 | </div> --> | 95 | </div> --> |
| 82 | - <div style="padding: 0.75rem 1rem;"> | 96 | + <div style="padding: 0.75rem 1rem"> |
| 83 | - <van-image round width="2.8rem" height="2.8rem" | 97 | + <van-image |
| 84 | - :src="item ? item : 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'" fit="cover" | 98 | + round |
| 85 | - v-for="(item, index) in teamAvatars" :key="index" | 99 | + width="2.8rem" |
| 86 | - :style="{ marginLeft: index > 0 ? '-0.5rem' : '', border: '2px solid #eff6ff', background: '#fff' }" /> | 100 | + height="2.8rem" |
| 101 | + :src="item ? item : 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'" | ||
| 102 | + fit="cover" | ||
| 103 | + v-for="(item, index) in teamAvatars" | ||
| 104 | + :key="index" | ||
| 105 | + :style="{ | ||
| 106 | + marginLeft: index > 0 ? '-0.5rem' : '', | ||
| 107 | + border: '2px solid #eff6ff', | ||
| 108 | + background: '#fff', | ||
| 109 | + }" | ||
| 110 | + /> | ||
| 87 | </div> | 111 | </div> |
| 88 | </div> | 112 | </div> |
| 89 | </div> | 113 | </div> |
| 90 | 114 | ||
| 91 | <div class="text-wrapper"> | 115 | <div class="text-wrapper"> |
| 92 | <div class="text-header">打卡动态</div> | 116 | <div class="text-header">打卡动态</div> |
| 93 | - <van-list v-if="checkinDataList.length" v-model:loading="loading" :finished="finished" finished-text="没有更多了" | 117 | + <van-list |
| 94 | - @load="onLoad" class="py-3 space-y-4"> | 118 | + v-if="checkinDataList.length" |
| 95 | - <CheckinCard v-for="post in checkinDataList" :key="post.id" :post="post" :use-cdn-optimization="true" | 119 | + v-model:loading="loading" |
| 96 | - :ref="(el) => setCheckinCardRef(el, post.id)" @like="handLike" @edit="editCheckin" @delete="delCheckin" | 120 | + :finished="finished" |
| 97 | - @video-play="handleVideoPlay" @audio-play="handleAudioPlay"> | 121 | + finished-text="没有更多了" |
| 122 | + @load="onLoad" | ||
| 123 | + class="space-y-4 py-3" | ||
| 124 | + > | ||
| 125 | + <CheckinCard | ||
| 126 | + v-for="post in checkinDataList" | ||
| 127 | + :key="post.id" | ||
| 128 | + :post="post" | ||
| 129 | + :use-cdn-optimization="true" | ||
| 130 | + :ref="el => setCheckinCardRef(el, post.id)" | ||
| 131 | + @like="handLike" | ||
| 132 | + @edit="editCheckin" | ||
| 133 | + @delete="delCheckin" | ||
| 134 | + @video-play="handleVideoPlay" | ||
| 135 | + @audio-play="handleAudioPlay" | ||
| 136 | + > | ||
| 98 | <template #content-top> | 137 | <template #content-top> |
| 99 | - <div class="text-gray-500 font-bold text-sm mb-4">{{ post.subtask_title }}</div> | 138 | + <div class="mb-4 text-sm font-bold text-gray-500">{{ post.subtask_title }}</div> |
| 100 | </template> | 139 | </template> |
| 101 | </CheckinCard> | 140 | </CheckinCard> |
| 102 | </van-list> | 141 | </van-list> |
| 103 | <van-empty v-else description="暂无数据" /> | 142 | <van-empty v-else description="暂无数据" /> |
| 104 | </div> | 143 | </div> |
| 105 | 144 | ||
| 106 | - <div style="height: 10rem;"></div> | 145 | + <div style="height: 10rem"></div> |
| 107 | - </div> <!-- 闭合 scrollable-content --> | 146 | + </div> |
| 147 | + <!-- 闭合 scrollable-content --> | ||
| 108 | </van-config-provider> | 148 | </van-config-provider> |
| 109 | 149 | ||
| 110 | <van-dialog v-model:show="dialog_show" title="标题" show-cancel-button></van-dialog> | 150 | <van-dialog v-model:show="dialog_show" title="标题" show-cancel-button></van-dialog> |
| ... | @@ -112,8 +152,11 @@ | ... | @@ -112,8 +152,11 @@ |
| 112 | <van-back-top right="5vw" bottom="25vh" offset="600" /> | 152 | <van-back-top right="5vw" bottom="25vh" offset="600" /> |
| 113 | 153 | ||
| 114 | <!-- 底部悬浮打卡按钮 --> | 154 | <!-- 底部悬浮打卡按钮 --> |
| 115 | - <div v-if="is_task_detail_ready && !is_task_finished" class="floating-checkin-button" | 155 | + <div |
| 116 | - :class="{ 'is-compact': isCompactButton }"> | 156 | + v-if="is_task_detail_ready && !is_task_finished" |
| 157 | + class="floating-checkin-button" | ||
| 158 | + :class="{ 'is-compact': isCompactButton }" | ||
| 159 | + > | ||
| 117 | <van-button type="primary" round @click="goToCheckinDetailPage" class="checkin-action-button"> | 160 | <van-button type="primary" round @click="goToCheckinDetailPage" class="checkin-action-button"> |
| 118 | <van-icon name="edit" size="1.2rem" /> | 161 | <van-icon name="edit" size="1.2rem" /> |
| 119 | <span class="button-text">我要打卡</span> | 162 | <span class="button-text">我要打卡</span> |
| ... | @@ -124,29 +167,45 @@ | ... | @@ -124,29 +167,45 @@ |
| 124 | 167 | ||
| 125 | <script> | 168 | <script> |
| 126 | export default { | 169 | export default { |
| 127 | - name: 'IndexCheckInPage' | 170 | + name: 'IndexCheckInPage', |
| 128 | } | 171 | } |
| 129 | </script> | 172 | </script> |
| 130 | 173 | ||
| 131 | <script setup> | 174 | <script setup> |
| 132 | -import { ref, onBeforeUnmount, onMounted, computed, nextTick, getCurrentInstance, onActivated, onDeactivated } from 'vue' | 175 | +import { |
| 176 | + ref, | ||
| 177 | + onBeforeUnmount, | ||
| 178 | + onMounted, | ||
| 179 | + computed, | ||
| 180 | + nextTick, | ||
| 181 | + getCurrentInstance, | ||
| 182 | + onActivated, | ||
| 183 | + onDeactivated, | ||
| 184 | +} from 'vue' | ||
| 133 | import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router' | 185 | import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router' |
| 134 | -import { showConfirmDialog, showSuccessToast, showFailToast, showLoadingToast } from 'vant'; | 186 | +import { showConfirmDialog, showSuccessToast, showFailToast, showLoadingToast } from 'vant' |
| 135 | -import AppLayout from "@/components/layout/AppLayout.vue"; | 187 | +import AppLayout from '@/components/layout/AppLayout.vue' |
| 136 | -import FrostedGlass from "@/components/effects/FrostedGlass.vue"; | 188 | +import FrostedGlass from '@/components/effects/FrostedGlass.vue' |
| 137 | -import CollapsibleCalendar from "@/components/calendar/CollapsibleCalendar.vue"; | 189 | +import CollapsibleCalendar from '@/components/calendar/CollapsibleCalendar.vue' |
| 138 | -import PostCountModel from "@/components/count/postCountModel.vue"; | 190 | +import PostCountModel from '@/components/count/postCountModel.vue' |
| 139 | -import CheckinCard from "@/components/checkin/CheckinCard.vue"; | 191 | +import CheckinCard from '@/components/checkin/CheckinCard.vue' |
| 140 | -import { useScrollRestoration } from "@/composables/useScrollRestoration"; | 192 | +import { useScrollRestoration } from '@/composables/useScrollRestoration' |
| 141 | -import { useTitle, useResizeObserver, useScroll } from '@vueuse/core'; | 193 | +import { useTitle, useResizeObserver, useScroll } from '@vueuse/core' |
| 142 | -import dayjs from 'dayjs'; | 194 | +import dayjs from 'dayjs' |
| 143 | - | 195 | + |
| 144 | -import { getTaskDetailAPI, getUploadTaskListAPI, delUploadTaskInfoAPI, likeUploadTaskInfoAPI, dislikeUploadTaskInfoAPI, getUploadTaskInfoAPI } from "@/api/checkin"; | 196 | +import { |
| 145 | -import { getTeacherFindSettingsAPI } from "@/api/teacher"; | 197 | + getTaskDetailAPI, |
| 198 | + getUploadTaskListAPI, | ||
| 199 | + delUploadTaskInfoAPI, | ||
| 200 | + likeUploadTaskInfoAPI, | ||
| 201 | + dislikeUploadTaskInfoAPI, | ||
| 202 | + getUploadTaskInfoAPI, | ||
| 203 | +} from '@/api/checkin' | ||
| 204 | +import { getTeacherFindSettingsAPI } from '@/api/teacher' | ||
| 146 | 205 | ||
| 147 | const route = useRoute() | 206 | const route = useRoute() |
| 148 | const router = useRouter() | 207 | const router = useRouter() |
| 149 | -useTitle(route.meta.title); | 208 | +useTitle(route.meta.title) |
| 150 | 209 | ||
| 151 | // 滚动监听 | 210 | // 滚动监听 |
| 152 | const { y } = useScroll(window) | 211 | const { y } = useScroll(window) |
| ... | @@ -161,73 +220,73 @@ const is_task_finished = computed(() => { | ... | @@ -161,73 +220,73 @@ const is_task_finished = computed(() => { |
| 161 | // 动态字段文字 | 220 | // 动态字段文字 |
| 162 | const dynamicFieldText = ref('感恩') | 221 | const dynamicFieldText = ref('感恩') |
| 163 | 222 | ||
| 164 | -const myRefCalendar = ref(null); | 223 | +const myRefCalendar = ref(null) |
| 165 | 224 | ||
| 166 | // 窗口尺寸相关的响应式数据 | 225 | // 窗口尺寸相关的响应式数据 |
| 167 | -const windowHeight = ref(window.innerHeight); | 226 | +const windowHeight = ref(window.innerHeight) |
| 168 | -const windowWidth = ref(window.innerWidth); | 227 | +const windowWidth = ref(window.innerWidth) |
| 169 | 228 | ||
| 170 | // 日历高度相关的响应式数据 | 229 | // 日历高度相关的响应式数据 |
| 171 | -const calendarRef = ref(null); | 230 | +const calendarRef = ref(null) |
| 172 | -const fixedCalendarWrapper = ref(null); | 231 | +const fixedCalendarWrapper = ref(null) |
| 173 | -const calendarHeight = ref(200); // 默认高度 | 232 | +const calendarHeight = ref(200) // 默认高度 |
| 174 | 233 | ||
| 175 | // 使用 ResizeObserver 监听日历容器高度变化 | 234 | // 使用 ResizeObserver 监听日历容器高度变化 |
| 176 | -useResizeObserver(fixedCalendarWrapper, (entries) => { | 235 | +useResizeObserver(fixedCalendarWrapper, entries => { |
| 177 | - const entry = entries[0]; | 236 | + const entry = entries[0] |
| 178 | if (entry && entry.target) { | 237 | if (entry && entry.target) { |
| 179 | // 使用 getBoundingClientRect 获取包含 padding 和 border 的完整高度 | 238 | // 使用 getBoundingClientRect 获取包含 padding 和 border 的完整高度 |
| 180 | - calendarHeight.value = entry.target.getBoundingClientRect().height; | 239 | + calendarHeight.value = entry.target.getBoundingClientRect().height |
| 181 | } | 240 | } |
| 182 | -}); | 241 | +}) |
| 183 | 242 | ||
| 184 | /** | 243 | /** |
| 185 | * 监听窗口尺寸变化 | 244 | * 监听窗口尺寸变化 |
| 186 | */ | 245 | */ |
| 187 | const handleResize = () => { | 246 | const handleResize = () => { |
| 188 | - windowHeight.value = window.innerHeight; | 247 | + windowHeight.value = window.innerHeight |
| 189 | - windowWidth.value = window.innerWidth; | 248 | + windowWidth.value = window.innerWidth |
| 190 | -}; | 249 | +} |
| 191 | 250 | ||
| 192 | // 组件挂载时添加事件监听 | 251 | // 组件挂载时添加事件监听 |
| 193 | onMounted(() => { | 252 | onMounted(() => { |
| 194 | - window.addEventListener('resize', handleResize); | 253 | + window.addEventListener('resize', handleResize) |
| 195 | // 监听屏幕方向变化(移动端) | 254 | // 监听屏幕方向变化(移动端) |
| 196 | window.addEventListener('orientationchange', () => { | 255 | window.addEventListener('orientationchange', () => { |
| 197 | // 延迟更新,等待方向变化完成 | 256 | // 延迟更新,等待方向变化完成 |
| 198 | - setTimeout(handleResize, 100); | 257 | + setTimeout(handleResize, 100) |
| 199 | - }); | 258 | + }) |
| 200 | -}); | 259 | +}) |
| 201 | 260 | ||
| 202 | // CheckinCard refs | 261 | // CheckinCard refs |
| 203 | -const checkinCardRefs = ref(new Map()); | 262 | +const checkinCardRefs = ref(new Map()) |
| 204 | const setCheckinCardRef = (el, id) => { | 263 | const setCheckinCardRef = (el, id) => { |
| 205 | - if (el) checkinCardRefs.value.set(id, el); | 264 | + if (el) checkinCardRefs.value.set(id, el) |
| 206 | -}; | 265 | +} |
| 207 | 266 | ||
| 208 | // 组件卸载前清理播放器引用和事件监听器 | 267 | // 组件卸载前清理播放器引用和事件监听器 |
| 209 | onBeforeUnmount(() => { | 268 | onBeforeUnmount(() => { |
| 210 | // 清理事件监听器 | 269 | // 清理事件监听器 |
| 211 | - window.removeEventListener('resize', handleResize); | 270 | + window.removeEventListener('resize', handleResize) |
| 212 | - window.removeEventListener('orientationchange', handleResize); | 271 | + window.removeEventListener('orientationchange', handleResize) |
| 213 | -}); | 272 | +}) |
| 214 | 273 | ||
| 215 | // 视频播放事件处理 | 274 | // 视频播放事件处理 |
| 216 | const handleVideoPlay = ({ post, player, videoId }) => { | 275 | const handleVideoPlay = ({ post, player, videoId }) => { |
| 217 | checkinCardRefs.value.forEach((card, id) => { | 276 | checkinCardRefs.value.forEach((card, id) => { |
| 218 | if (id !== post.id) { | 277 | if (id !== post.id) { |
| 219 | - card.stopAllMedia(); | 278 | + card.stopAllMedia() |
| 220 | } | 279 | } |
| 221 | - }); | 280 | + }) |
| 222 | } | 281 | } |
| 223 | 282 | ||
| 224 | // 音频播放事件处理 | 283 | // 音频播放事件处理 |
| 225 | const handleAudioPlay = ({ post, player }) => { | 284 | const handleAudioPlay = ({ post, player }) => { |
| 226 | checkinCardRefs.value.forEach((card, id) => { | 285 | checkinCardRefs.value.forEach((card, id) => { |
| 227 | if (id !== post.id) { | 286 | if (id !== post.id) { |
| 228 | - card.stopAllMedia(); | 287 | + card.stopAllMedia() |
| 229 | } | 288 | } |
| 230 | - }); | 289 | + }) |
| 231 | } | 290 | } |
| 232 | 291 | ||
| 233 | const themeVars = { | 292 | const themeVars = { |
| ... | @@ -236,30 +295,29 @@ const themeVars = { | ... | @@ -236,30 +295,29 @@ const themeVars = { |
| 236 | calendarInfoLineHeight: '0.3rem', | 295 | calendarInfoLineHeight: '0.3rem', |
| 237 | } | 296 | } |
| 238 | 297 | ||
| 239 | -const progress1 = ref(0); | 298 | +const progress1 = ref(0) |
| 240 | // const progress2 = ref(76); | 299 | // const progress2 = ref(76); |
| 241 | 300 | ||
| 242 | const teamAvatars = ref([]) | 301 | const teamAvatars = ref([]) |
| 243 | 302 | ||
| 244 | - | ||
| 245 | /** | 303 | /** |
| 246 | * 日历日期格式化函数 | 304 | * 日历日期格式化函数 |
| 247 | * @param {Object} day - 日期对象 | 305 | * @param {Object} day - 日期对象 |
| 248 | * @returns {Object} 格式化后的日期对象 | 306 | * @returns {Object} 格式化后的日期对象 |
| 249 | */ | 307 | */ |
| 250 | -const formatter = (day) => { | 308 | +const formatter = day => { |
| 251 | - const year = day.date.getFullYear(); | 309 | + const year = day.date.getFullYear() |
| 252 | - const month = day.date.getMonth() + 1; | 310 | + const month = day.date.getMonth() + 1 |
| 253 | - const date = day.date.getDate(); | 311 | + const date = day.date.getDate() |
| 254 | 312 | ||
| 255 | - let checkin_days = myCheckinDates.value; | 313 | + const checkin_days = myCheckinDates.value |
| 256 | - let fill_checkin_days = myFillCheckinDates.value; | 314 | + const fill_checkin_days = myFillCheckinDates.value |
| 257 | 315 | ||
| 258 | // 禁用未来日期 | 316 | // 禁用未来日期 |
| 259 | if (dayjs(day.date).isAfter(new Date(), 'day')) { | 317 | if (dayjs(day.date).isAfter(new Date(), 'day')) { |
| 260 | - day.type = 'disabled'; | 318 | + day.type = 'disabled' |
| 261 | - day.className = 'calendar-disabled'; | 319 | + day.className = 'calendar-disabled' |
| 262 | - return day; | 320 | + return day |
| 263 | } | 321 | } |
| 264 | 322 | ||
| 265 | // 如果选中的是全部作业,不执行打卡状态检查 | 323 | // 如果选中的是全部作业,不执行打卡状态检查 |
| ... | @@ -267,17 +325,17 @@ const formatter = (day) => { | ... | @@ -267,17 +325,17 @@ const formatter = (day) => { |
| 267 | // 检查当前日期是否在签到日期列表中 | 325 | // 检查当前日期是否在签到日期列表中 |
| 268 | if (checkin_days && checkin_days.length > 0) { | 326 | if (checkin_days && checkin_days.length > 0) { |
| 269 | // 格式化当前日期为YYYY-MM-DD格式,与checkin_days中的格式匹配 | 327 | // 格式化当前日期为YYYY-MM-DD格式,与checkin_days中的格式匹配 |
| 270 | - const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${date.toString().padStart(2, '0')}`; | 328 | + const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${date.toString().padStart(2, '0')}` |
| 271 | 329 | ||
| 272 | // 检查是否已打卡 | 330 | // 检查是否已打卡 |
| 273 | if (checkin_days.includes(formattedDate)) { | 331 | if (checkin_days.includes(formattedDate)) { |
| 274 | - day.type = 'selected'; | 332 | + day.type = 'selected' |
| 275 | - day.bottomInfo = '已打卡'; | 333 | + day.bottomInfo = '已打卡' |
| 276 | // 如果是当前选中的已打卡日期,使用特殊样式 | 334 | // 如果是当前选中的已打卡日期,使用特殊样式 |
| 277 | if (selectedDate.value === formattedDate) { | 335 | if (selectedDate.value === formattedDate) { |
| 278 | - day.className = 'calendar-selected'; | 336 | + day.className = 'calendar-selected' |
| 279 | } else { | 337 | } else { |
| 280 | - day.className = 'calendar-checkin'; | 338 | + day.className = 'calendar-checkin' |
| 281 | } | 339 | } |
| 282 | } | 340 | } |
| 283 | } | 341 | } |
| ... | @@ -285,18 +343,18 @@ const formatter = (day) => { | ... | @@ -285,18 +343,18 @@ const formatter = (day) => { |
| 285 | // 检查当前日期是否在补卡日期列表中 | 343 | // 检查当前日期是否在补卡日期列表中 |
| 286 | if (fill_checkin_days && fill_checkin_days.length > 0) { | 344 | if (fill_checkin_days && fill_checkin_days.length > 0) { |
| 287 | // 格式化当前日期为YYYY-MM-DD格式,与fill_checkin_days中的格式匹配 | 345 | // 格式化当前日期为YYYY-MM-DD格式,与fill_checkin_days中的格式匹配 |
| 288 | - const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${date.toString().padStart(2, '0')}`; | 346 | + const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${date.toString().padStart(2, '0')}` |
| 289 | // 检查是否已补卡 | 347 | // 检查是否已补卡 |
| 290 | if (fill_checkin_days.includes(formattedDate)) { | 348 | if (fill_checkin_days.includes(formattedDate)) { |
| 291 | // 如果是当前选中的已补卡日期,使用特殊样式 | 349 | // 如果是当前选中的已补卡日期,使用特殊样式 |
| 292 | - day.type = 'selected'; | 350 | + day.type = 'selected' |
| 293 | - day.bottomInfo = '待补卡'; | 351 | + day.bottomInfo = '待补卡' |
| 294 | if (selectedDate.value === formattedDate) { | 352 | if (selectedDate.value === formattedDate) { |
| 295 | - day.className = 'calendar-selected'; | 353 | + day.className = 'calendar-selected' |
| 296 | // 选中的是补卡日期 | 354 | // 选中的是补卡日期 |
| 297 | - isPatchCheckin.value = true; | 355 | + isPatchCheckin.value = true |
| 298 | } else { | 356 | } else { |
| 299 | - day.className = 'calendar-fill-checkin'; | 357 | + day.className = 'calendar-fill-checkin' |
| 300 | } | 358 | } |
| 301 | } | 359 | } |
| 302 | } | 360 | } |
| ... | @@ -304,16 +362,16 @@ const formatter = (day) => { | ... | @@ -304,16 +362,16 @@ const formatter = (day) => { |
| 304 | 362 | ||
| 305 | // 选中今天的日期 | 363 | // 选中今天的日期 |
| 306 | if (dayjs(day.date).isSame(new Date(), 'day')) { | 364 | if (dayjs(day.date).isSame(new Date(), 'day')) { |
| 307 | - day.className = 'calendar-today'; | 365 | + day.className = 'calendar-today' |
| 308 | - day.type = 'selected'; | 366 | + day.type = 'selected' |
| 309 | - day.bottomInfo = '今日'; | 367 | + day.bottomInfo = '今日' |
| 310 | } | 368 | } |
| 311 | 369 | ||
| 312 | - return day; | 370 | + return day |
| 313 | } | 371 | } |
| 314 | 372 | ||
| 315 | // 添加一个响应式变量来存储当前选中的日期 | 373 | // 添加一个响应式变量来存储当前选中的日期 |
| 316 | -const selectedDate = ref(new Date()); | 374 | +const selectedDate = ref(new Date()) |
| 317 | 375 | ||
| 318 | const hasUserSelectedDate = ref(!!route.query.date) | 376 | const hasUserSelectedDate = ref(!!route.query.date) |
| 319 | const isInitializing = ref(true) | 377 | const isInitializing = ref(true) |
| ... | @@ -323,7 +381,7 @@ const onClearSelectedDate = () => { | ... | @@ -323,7 +381,7 @@ const onClearSelectedDate = () => { |
| 323 | delete next_query.date | 381 | delete next_query.date |
| 324 | router.replace({ | 382 | router.replace({ |
| 325 | path: route.path, | 383 | path: route.path, |
| 326 | - query: next_query | 384 | + query: next_query, |
| 327 | }) | 385 | }) |
| 328 | 386 | ||
| 329 | hasUserSelectedDate.value = false | 387 | hasUserSelectedDate.value = false |
| ... | @@ -336,19 +394,19 @@ const onClearSelectedDate = () => { | ... | @@ -336,19 +394,19 @@ const onClearSelectedDate = () => { |
| 336 | onLoad(null, false) | 394 | onLoad(null, false) |
| 337 | } | 395 | } |
| 338 | 396 | ||
| 339 | -const onSelectDay = (day) => { | 397 | +const onSelectDay = day => { |
| 340 | - const currentSelectedDate = dayjs(day).format('YYYY-MM-DD'); | 398 | + const currentSelectedDate = dayjs(day).format('YYYY-MM-DD') |
| 341 | 399 | ||
| 342 | if (isInitializing.value && !hasUserSelectedDate.value) { | 400 | if (isInitializing.value && !hasUserSelectedDate.value) { |
| 343 | - selectedDate.value = currentSelectedDate; | 401 | + selectedDate.value = currentSelectedDate |
| 344 | - return; | 402 | + return |
| 345 | } | 403 | } |
| 346 | 404 | ||
| 347 | - getTaskDetail(dayjs(day).format('YYYY-MM')); | 405 | + getTaskDetail(dayjs(day).format('YYYY-MM')) |
| 348 | 406 | ||
| 349 | // 更新当前选中的日期 | 407 | // 更新当前选中的日期 |
| 350 | - selectedDate.value = currentSelectedDate; | 408 | + selectedDate.value = currentSelectedDate |
| 351 | - hasUserSelectedDate.value = true; | 409 | + hasUserSelectedDate.value = true |
| 352 | 410 | ||
| 353 | // 修改浏览器地址把当前的date加入地址栏, 页面不刷新 | 411 | // 修改浏览器地址把当前的date加入地址栏, 页面不刷新 |
| 354 | // 使用replace替代push,避免在浏览器历史记录中添加多个条目 | 412 | // 使用replace替代push,避免在浏览器历史记录中添加多个条目 |
| ... | @@ -356,8 +414,8 @@ const onSelectDay = (day) => { | ... | @@ -356,8 +414,8 @@ const onSelectDay = (day) => { |
| 356 | path: route.path, | 414 | path: route.path, |
| 357 | query: { | 415 | query: { |
| 358 | ...route.query, | 416 | ...route.query, |
| 359 | - date: currentSelectedDate | 417 | + date: currentSelectedDate, |
| 360 | - } | 418 | + }, |
| 361 | }) | 419 | }) |
| 362 | // 重置分页参数 | 420 | // 重置分页参数 |
| 363 | page.value = 0 | 421 | page.value = 0 |
| ... | @@ -367,7 +425,7 @@ const onSelectDay = (day) => { | ... | @@ -367,7 +425,7 @@ const onSelectDay = (day) => { |
| 367 | onLoad(currentSelectedDate, true) | 425 | onLoad(currentSelectedDate, true) |
| 368 | } | 426 | } |
| 369 | 427 | ||
| 370 | -const onClickSubtitle = (evt) => { | 428 | +const onClickSubtitle = evt => { |
| 371 | // console.warn('点击了日期标题', evt); | 429 | // console.warn('点击了日期标题', evt); |
| 372 | } | 430 | } |
| 373 | 431 | ||
| ... | @@ -377,7 +435,7 @@ const onClickSubtitle = (evt) => { | ... | @@ -377,7 +435,7 @@ const onClickSubtitle = (evt) => { |
| 377 | */ | 435 | */ |
| 378 | const onPanelChange = ({ date }) => { | 436 | const onPanelChange = ({ date }) => { |
| 379 | // console.warn('面板切换', date); | 437 | // console.warn('面板切换', date); |
| 380 | - getTaskDetail(dayjs(date).format('YYYY-MM')); | 438 | + getTaskDetail(dayjs(date).format('YYYY-MM')) |
| 381 | } | 439 | } |
| 382 | 440 | ||
| 383 | /** | 441 | /** |
| ... | @@ -386,16 +444,19 @@ const onPanelChange = ({ date }) => { | ... | @@ -386,16 +444,19 @@ const onPanelChange = ({ date }) => { |
| 386 | */ | 444 | */ |
| 387 | const selectedSubtaskId = ref('') | 445 | const selectedSubtaskId = ref('') |
| 388 | 446 | ||
| 389 | -const onSelectCourse = (course) => { | 447 | +const onSelectCourse = course => { |
| 390 | - selectedSubtaskId.value = course; | 448 | + selectedSubtaskId.value = course |
| 391 | // 切换作业后, 刷新当前日期的打卡详情 | 449 | // 切换作业后, 刷新当前日期的打卡详情 |
| 392 | - getTaskDetail(dayjs(selectedDate.value).format('YYYY-MM')); | 450 | + getTaskDetail(dayjs(selectedDate.value).format('YYYY-MM')) |
| 393 | // 重置分页参数 | 451 | // 重置分页参数 |
| 394 | page.value = 0 | 452 | page.value = 0 |
| 395 | checkinDataList.value = [] | 453 | checkinDataList.value = [] |
| 396 | finished.value = false | 454 | finished.value = false |
| 397 | // 重新加载数据 | 455 | // 重新加载数据 |
| 398 | - onLoad(hasUserSelectedDate.value ? (route.query.date || selectedDate.value) : null, hasUserSelectedDate.value) | 456 | + onLoad( |
| 457 | + hasUserSelectedDate.value ? route.query.date || selectedDate.value : null, | ||
| 458 | + hasUserSelectedDate.value | ||
| 459 | + ) | ||
| 399 | } | 460 | } |
| 400 | 461 | ||
| 401 | /** | 462 | /** |
| ... | @@ -403,15 +464,15 @@ const onSelectCourse = (course) => { | ... | @@ -403,15 +464,15 @@ const onSelectCourse = (course) => { |
| 403 | * @param {string} type - 打卡类型 | 464 | * @param {string} type - 打卡类型 |
| 404 | * @returns {string} 图标名称 | 465 | * @returns {string} 图标名称 |
| 405 | */ | 466 | */ |
| 406 | -const getIconName = (type) => { | 467 | +const getIconName = type => { |
| 407 | const iconMap = { | 468 | const iconMap = { |
| 408 | - 'text': 'edit', | 469 | + text: 'edit', |
| 409 | - 'image': 'photo', | 470 | + image: 'photo', |
| 410 | - 'video': 'video', | 471 | + video: 'video', |
| 411 | - 'audio': 'music' | 472 | + audio: 'music', |
| 412 | - }; | 473 | + } |
| 413 | - return iconMap[type] || 'edit'; | 474 | + return iconMap[type] || 'edit' |
| 414 | -}; | 475 | +} |
| 415 | 476 | ||
| 416 | /** | 477 | /** |
| 417 | * 处理打卡类型点击事件 | 478 | * 处理打卡类型点击事件 |
| ... | @@ -445,8 +506,11 @@ const getIconName = (type) => { | ... | @@ -445,8 +506,11 @@ const getIconName = (type) => { |
| 445 | * 业务逻辑调整, 需要把打卡类型带到下一页判断 | 506 | * 业务逻辑调整, 需要把打卡类型带到下一页判断 |
| 446 | */ | 507 | */ |
| 447 | const goToCheckinDetailPage = () => { | 508 | const goToCheckinDetailPage = () => { |
| 448 | - const current_date = route.query.date || dayjs().format('YYYY-MM-DD'); | 509 | + const current_date = route.query.date || dayjs().format('YYYY-MM-DD') |
| 449 | - save_checkin_scroll_state({ return_full_path: route.fullPath, calendar_height: get_calendar_offset() }) | 510 | + save_checkin_scroll_state({ |
| 511 | + return_full_path: route.fullPath, | ||
| 512 | + calendar_height: get_calendar_offset(), | ||
| 513 | + }) | ||
| 450 | router.push({ | 514 | router.push({ |
| 451 | path: '/checkin/detail', | 515 | path: '/checkin/detail', |
| 452 | query: { | 516 | query: { |
| ... | @@ -456,31 +520,35 @@ const goToCheckinDetailPage = () => { | ... | @@ -456,31 +520,35 @@ const goToCheckinDetailPage = () => { |
| 456 | date: current_date, | 520 | date: current_date, |
| 457 | is_patch: isPatchCheckin.value ? '1' : '0', | 521 | is_patch: isPatchCheckin.value ? '1' : '0', |
| 458 | task_type: taskDetail.value.task_type, | 522 | task_type: taskDetail.value.task_type, |
| 459 | - } | 523 | + }, |
| 460 | }) | 524 | }) |
| 461 | } | 525 | } |
| 462 | 526 | ||
| 463 | -const handLike = async (post) => { | 527 | +const handLike = async post => { |
| 464 | if (!post.is_liked) { | 528 | if (!post.is_liked) { |
| 465 | - const { code, data } = await likeUploadTaskInfoAPI({ checkin_id: post.id, }) | 529 | + const { code, data } = await likeUploadTaskInfoAPI({ checkin_id: post.id }) |
| 466 | if (code === 1) { | 530 | if (code === 1) { |
| 467 | showSuccessToast('点赞成功') | 531 | showSuccessToast('点赞成功') |
| 468 | - post.likes++; | 532 | + post.likes++ |
| 469 | - post.is_liked = true; | 533 | + post.is_liked = true |
| 470 | } | 534 | } |
| 471 | } else { | 535 | } else { |
| 472 | - const { code, data } = await dislikeUploadTaskInfoAPI({ checkin_id: post.id, }) | 536 | + const { code, data } = await dislikeUploadTaskInfoAPI({ checkin_id: post.id }) |
| 473 | if (code === 1) { | 537 | if (code === 1) { |
| 474 | showSuccessToast('取消点赞成功') | 538 | showSuccessToast('取消点赞成功') |
| 475 | - post.likes--; | 539 | + post.likes-- |
| 476 | - post.is_liked = false; | 540 | + post.is_liked = false |
| 477 | } | 541 | } |
| 478 | } | 542 | } |
| 479 | } | 543 | } |
| 480 | 544 | ||
| 481 | -const editCheckin = (post) => { | 545 | +const editCheckin = post => { |
| 482 | // 统一跳转到CheckinDetailPage页面处理所有类型的编辑 | 546 | // 统一跳转到CheckinDetailPage页面处理所有类型的编辑 |
| 483 | - save_checkin_scroll_state({ anchor_id: post.id, return_full_path: route.fullPath, calendar_height: get_calendar_offset() }) | 547 | + save_checkin_scroll_state({ |
| 548 | + anchor_id: post.id, | ||
| 549 | + return_full_path: route.fullPath, | ||
| 550 | + calendar_height: get_calendar_offset(), | ||
| 551 | + }) | ||
| 484 | router.push({ | 552 | router.push({ |
| 485 | path: '/checkin/detail', | 553 | path: '/checkin/detail', |
| 486 | query: { | 554 | query: { |
| ... | @@ -490,11 +558,11 @@ const editCheckin = (post) => { | ... | @@ -490,11 +558,11 @@ const editCheckin = (post) => { |
| 490 | type: post.file_type, | 558 | type: post.file_type, |
| 491 | task_type: taskDetail.value.task_type, | 559 | task_type: taskDetail.value.task_type, |
| 492 | status: 'edit', | 560 | status: 'edit', |
| 493 | - } | 561 | + }, |
| 494 | }) | 562 | }) |
| 495 | } | 563 | } |
| 496 | 564 | ||
| 497 | -const delCheckin = (post) => { | 565 | +const delCheckin = post => { |
| 498 | showConfirmDialog({ | 566 | showConfirmDialog({ |
| 499 | title: '温馨提示', | 567 | title: '温馨提示', |
| 500 | message: '您是否确定要删除该动态?', | 568 | message: '您是否确定要删除该动态?', |
| ... | @@ -502,68 +570,75 @@ const delCheckin = (post) => { | ... | @@ -502,68 +570,75 @@ const delCheckin = (post) => { |
| 502 | }) | 570 | }) |
| 503 | .then(async () => { | 571 | .then(async () => { |
| 504 | // 调用接口 | 572 | // 调用接口 |
| 505 | - const { code, data } = await delUploadTaskInfoAPI({ i: post.id }); | 573 | + const { code, data } = await delUploadTaskInfoAPI({ i: post.id }) |
| 506 | if (code === 1) { | 574 | if (code === 1) { |
| 507 | // 删除成功后,刷新页面 | 575 | // 删除成功后,刷新页面 |
| 508 | - showSuccessToast('删除成功'); | 576 | + showSuccessToast('删除成功') |
| 509 | 577 | ||
| 510 | // 优化:使用 splice 替代 filter,避免大数组重新分配内存,性能更好 | 578 | // 优化:使用 splice 替代 filter,避免大数组重新分配内存,性能更好 |
| 511 | // checkinDataList.value = checkinDataList.value.filter(item => item.id !== post.id); | 579 | // checkinDataList.value = checkinDataList.value.filter(item => item.id !== post.id); |
| 512 | - const index = checkinDataList.value.findIndex(item => item.id === post.id); | 580 | + const index = checkinDataList.value.findIndex(item => item.id === post.id) |
| 513 | if (index > -1) { | 581 | if (index > -1) { |
| 514 | - checkinDataList.value.splice(index, 1); | 582 | + checkinDataList.value.splice(index, 1) |
| 515 | } | 583 | } |
| 516 | 584 | ||
| 517 | // 检查是否还可以打卡 | 585 | // 检查是否还可以打卡 |
| 518 | - const current_date = route.query.date; | 586 | + const current_date = route.query.date |
| 519 | if (current_date) { | 587 | if (current_date) { |
| 520 | - getTaskDetail(dayjs(current_date).format('YYYY-MM')); | 588 | + getTaskDetail(dayjs(current_date).format('YYYY-MM')) |
| 521 | } else { | 589 | } else { |
| 522 | - getTaskDetail(dayjs().format('YYYY-MM')); | 590 | + getTaskDetail(dayjs().format('YYYY-MM')) |
| 523 | } | 591 | } |
| 524 | } else { | 592 | } else { |
| 525 | - showErrorToast('删除失败'); | 593 | + showErrorToast('删除失败') |
| 526 | } | 594 | } |
| 527 | }) | 595 | }) |
| 528 | .catch(() => { | 596 | .catch(() => { |
| 529 | // on cancel | 597 | // on cancel |
| 530 | - }); | 598 | + }) |
| 531 | } | 599 | } |
| 532 | 600 | ||
| 533 | -const taskDetail = ref({}); | 601 | +const taskDetail = ref({}) |
| 534 | -const myCheckinDates = ref([]); | 602 | +const myCheckinDates = ref([]) |
| 535 | -const myFillCheckinDates = ref([]); | 603 | +const myFillCheckinDates = ref([]) |
| 536 | -const checkinDataList = ref([]); | 604 | +const checkinDataList = ref([]) |
| 537 | -const showProgress = ref(true); | 605 | +const showProgress = ref(true) |
| 538 | // 是否为补卡模式 | 606 | // 是否为补卡模式 |
| 539 | -const isPatchCheckin = ref(false); | 607 | +const isPatchCheckin = ref(false) |
| 540 | 608 | ||
| 541 | // 作品类型选项 | 609 | // 作品类型选项 |
| 542 | -const attachmentTypeOptions = ref([]); | 610 | +const attachmentTypeOptions = ref([]) |
| 543 | 611 | ||
| 544 | // 今日计数次数 | 612 | // 今日计数次数 |
| 545 | -const myTodayGratitudeCount = ref(0); | 613 | +const myTodayGratitudeCount = ref(0) |
| 546 | // 累计计数次数 | 614 | // 累计计数次数 |
| 547 | -const myTotalGratitudeCount = ref(0); | 615 | +const myTotalGratitudeCount = ref(0) |
| 548 | 616 | ||
| 549 | -const getTaskDetail = async (month) => { | 617 | +const getTaskDetail = async month => { |
| 550 | - const { code, data } = await getTaskDetailAPI({ i: route.query.id, month, subtask_id: selectedSubtaskId.value }); | 618 | + const { code, data } = await getTaskDetailAPI({ |
| 619 | + i: route.query.id, | ||
| 620 | + month, | ||
| 621 | + subtask_id: selectedSubtaskId.value, | ||
| 622 | + }) | ||
| 551 | if (code === 1) { | 623 | if (code === 1) { |
| 552 | - taskDetail.value = data; | 624 | + taskDetail.value = data |
| 553 | is_task_detail_ready.value = true | 625 | is_task_detail_ready.value = true |
| 554 | - progress1.value = ((data.checkin_number / data.target_number) * 100).toFixed(1); // 计算进度条百分比 | 626 | + progress1.value = ((data.checkin_number / data.target_number) * 100).toFixed(1) // 计算进度条百分比 |
| 555 | - showProgress.value = !isNaN(progress1.value); // 如果是NaN,就不显示进度条 | 627 | + showProgress.value = !isNaN(progress1.value) // 如果是NaN,就不显示进度条 |
| 556 | - teamAvatars.value = taskDetail.value.checkin_avatars?.length > 8 ? taskDetail.value.checkin_avatars.splice(0, 8) : taskDetail.value.checkin_avatars; | 628 | + teamAvatars.value = |
| 629 | + taskDetail.value.checkin_avatars?.length > 8 | ||
| 630 | + ? taskDetail.value.checkin_avatars.splice(0, 8) | ||
| 631 | + : taskDetail.value.checkin_avatars | ||
| 557 | // 获取当前用户的打卡日期 | 632 | // 获取当前用户的打卡日期 |
| 558 | - myCheckinDates.value = data.my_checkin_dates; | 633 | + myCheckinDates.value = data.my_checkin_dates |
| 559 | // 获取当前用户的补卡日期 | 634 | // 获取当前用户的补卡日期 |
| 560 | - myFillCheckinDates.value = data.makeup_checkin_dates; | 635 | + myFillCheckinDates.value = data.makeup_checkin_dates |
| 561 | // 把['2025-06-06'] 转化为 [6] 只取日期去掉0 | 636 | // 把['2025-06-06'] 转化为 [6] 只取日期去掉0 |
| 562 | // myCheckinDates.value = myCheckinDates.value.map(date => { | 637 | // myCheckinDates.value = myCheckinDates.value.map(date => { |
| 563 | // return dayjs(date).date(); | 638 | // return dayjs(date).date(); |
| 564 | // }) | 639 | // }) |
| 565 | - myTodayGratitudeCount.value = data.my_today_gratitude_count || 0; | 640 | + myTodayGratitudeCount.value = data.my_today_gratitude_count || 0 |
| 566 | - myTotalGratitudeCount.value = data.my_total_gratitude_count || 0; | 641 | + myTotalGratitudeCount.value = data.my_total_gratitude_count || 0 |
| 567 | } | 642 | } |
| 568 | } | 643 | } |
| 569 | 644 | ||
| ... | @@ -573,87 +648,90 @@ const limit = ref(3) | ... | @@ -573,87 +648,90 @@ const limit = ref(3) |
| 573 | const page = ref(0) | 648 | const page = ref(0) |
| 574 | 649 | ||
| 575 | const onLoad = async (date, isUserInitiated) => { | 650 | const onLoad = async (date, isUserInitiated) => { |
| 576 | - const nextPage = page.value; | 651 | + const nextPage = page.value |
| 577 | - const current_date = date || route.query.date; | 652 | + const current_date = date || route.query.date |
| 578 | - const shouldPassDate = typeof isUserInitiated === 'boolean' ? isUserInitiated : hasUserSelectedDate.value | 653 | + const shouldPassDate = |
| 654 | + typeof isUserInitiated === 'boolean' ? isUserInitiated : hasUserSelectedDate.value | ||
| 579 | const params = { | 655 | const params = { |
| 580 | limit: limit.value, | 656 | limit: limit.value, |
| 581 | page: nextPage, | 657 | page: nextPage, |
| 582 | task_id: route.query.id, | 658 | task_id: route.query.id, |
| 583 | subtask_id: selectedSubtaskId.value, | 659 | subtask_id: selectedSubtaskId.value, |
| 584 | - }; | 660 | + } |
| 585 | if (current_date && shouldPassDate) { | 661 | if (current_date && shouldPassDate) { |
| 586 | - params.date = current_date; | 662 | + params.date = current_date |
| 587 | } | 663 | } |
| 588 | 664 | ||
| 589 | - const res = await getUploadTaskListAPI(params); | 665 | + const res = await getUploadTaskListAPI(params) |
| 590 | if (res.code === 1) { | 666 | if (res.code === 1) { |
| 591 | // 整理数据结构 | 667 | // 整理数据结构 |
| 592 | - const newItems = formatData(res.data); | 668 | + const newItems = formatData(res.data) |
| 593 | // 去重合并 | 669 | // 去重合并 |
| 594 | - const existingIds = new Set(checkinDataList.value.map(item => item.id)); | 670 | + const existingIds = new Set(checkinDataList.value.map(item => item.id)) |
| 595 | - const uniqueNewItems = newItems.filter(item => !existingIds.has(item.id)); | 671 | + const uniqueNewItems = newItems.filter(item => !existingIds.has(item.id)) |
| 596 | 672 | ||
| 597 | if (uniqueNewItems.length > 0) { | 673 | if (uniqueNewItems.length > 0) { |
| 598 | - checkinDataList.value.push(...uniqueNewItems); | 674 | + checkinDataList.value.push(...uniqueNewItems) |
| 599 | } | 675 | } |
| 600 | 676 | ||
| 601 | - finished.value = res.data.checkin_list.length < limit.value; | 677 | + finished.value = res.data.checkin_list.length < limit.value |
| 602 | - page.value = nextPage + 1; | 678 | + page.value = nextPage + 1 |
| 603 | } | 679 | } |
| 604 | - loading.value = false; | 680 | + loading.value = false |
| 605 | -}; | 681 | +} |
| 606 | 682 | ||
| 607 | -const initPage = async (date) => { | 683 | +const initPage = async date => { |
| 608 | // 重置数据 | 684 | // 重置数据 |
| 609 | - checkinDataList.value = []; | 685 | + checkinDataList.value = [] |
| 610 | - page.value = 0; | 686 | + page.value = 0 |
| 611 | - finished.value = false; | 687 | + finished.value = false |
| 612 | - loading.value = false; | 688 | + loading.value = false |
| 613 | - taskDetail.value = {}; | 689 | + taskDetail.value = {} |
| 614 | is_task_detail_ready.value = false | 690 | is_task_detail_ready.value = false |
| 615 | - selectedSubtaskId.value = ''; | 691 | + selectedSubtaskId.value = '' |
| 616 | 692 | ||
| 617 | - const current_date = date || route.query.date; | 693 | + const current_date = date || route.query.date |
| 618 | - hasUserSelectedDate.value = !!current_date; | 694 | + hasUserSelectedDate.value = !!current_date |
| 619 | if (current_date) { | 695 | if (current_date) { |
| 620 | - selectedDate.value = new Date(current_date); | 696 | + selectedDate.value = new Date(current_date) |
| 621 | - await getTaskDetail(dayjs(current_date).format('YYYY-MM')); | 697 | + await getTaskDetail(dayjs(current_date).format('YYYY-MM')) |
| 622 | // 确保日历组件已挂载再调用 reset | 698 | // 确保日历组件已挂载再调用 reset |
| 623 | nextTick(() => { | 699 | nextTick(() => { |
| 624 | - calendarRef.value?.reset(new Date(current_date)); | 700 | + calendarRef.value?.reset(new Date(current_date)) |
| 625 | }) | 701 | }) |
| 626 | - onLoad(current_date, true); | 702 | + onLoad(current_date, true) |
| 627 | } else { | 703 | } else { |
| 628 | - selectedDate.value = new Date(); | 704 | + selectedDate.value = new Date() |
| 629 | - await getTaskDetail(dayjs().format('YYYY-MM')); | 705 | + await getTaskDetail(dayjs().format('YYYY-MM')) |
| 630 | // 确保日历组件已挂载再调用 reset | 706 | // 确保日历组件已挂载再调用 reset |
| 631 | nextTick(() => { | 707 | nextTick(() => { |
| 632 | - calendarRef.value?.reset(new Date()); | 708 | + calendarRef.value?.reset(new Date()) |
| 633 | }) | 709 | }) |
| 634 | - onLoad(null, false); | 710 | + onLoad(null, false) |
| 635 | } | 711 | } |
| 636 | } | 712 | } |
| 637 | 713 | ||
| 638 | onMounted(async () => { | 714 | onMounted(async () => { |
| 639 | // 记录当前的taskId | 715 | // 记录当前的taskId |
| 640 | - lastTaskId.value = route.query.id; | 716 | + lastTaskId.value = route.query.id |
| 641 | - await initPage(route.query.date); | 717 | + await initPage(route.query.date) |
| 642 | nextTick(() => { | 718 | nextTick(() => { |
| 643 | - isInitializing.value = false; | 719 | + isInitializing.value = false |
| 644 | }) | 720 | }) |
| 645 | 721 | ||
| 646 | // 获取作品类型数据 | 722 | // 获取作品类型数据 |
| 647 | try { | 723 | try { |
| 648 | - const { code, data } = await getTeacherFindSettingsAPI(); | 724 | + const { code, data } = await getTeacherFindSettingsAPI() |
| 649 | if (code === 1 && data.task_attachment_type) { | 725 | if (code === 1 && data.task_attachment_type) { |
| 650 | - attachmentTypeOptions.value = Object.entries(data.task_attachment_type).map(([key, value]) => ({ | 726 | + attachmentTypeOptions.value = Object.entries(data.task_attachment_type).map( |
| 727 | + ([key, value]) => ({ | ||
| 651 | key, | 728 | key, |
| 652 | - value | 729 | + value, |
| 653 | - })); | 730 | + }) |
| 731 | + ) | ||
| 654 | } | 732 | } |
| 655 | } catch (error) { | 733 | } catch (error) { |
| 656 | - console.error('获取作品类型数据失败:', error); | 734 | + console.error('获取作品类型数据失败:', error) |
| 657 | } | 735 | } |
| 658 | }) | 736 | }) |
| 659 | 737 | ||
| ... | @@ -671,39 +749,52 @@ onBeforeRouteLeave((to, from) => { | ... | @@ -671,39 +749,52 @@ onBeforeRouteLeave((to, from) => { |
| 671 | } | 749 | } |
| 672 | }) | 750 | }) |
| 673 | 751 | ||
| 674 | -const formatData = (data) => { | 752 | +const formatData = data => { |
| 675 | - let formattedData = []; | 753 | + let formattedData = [] |
| 676 | formattedData = data?.checkin_list.map((item, index) => { | 754 | formattedData = data?.checkin_list.map((item, index) => { |
| 677 | - let images = []; | 755 | + const images = [] |
| 678 | - let audio = []; | 756 | + const audio = [] |
| 679 | - let videoList = []; | 757 | + const videoList = [] |
| 680 | 758 | ||
| 681 | // 支持多类型混合显示:遍历 files 数组根据 file_type 分类 | 759 | // 支持多类型混合显示:遍历 files 数组根据 file_type 分类 |
| 682 | if (item.files && Array.isArray(item.files)) { | 760 | if (item.files && Array.isArray(item.files)) { |
| 683 | item.files.forEach(file => { | 761 | item.files.forEach(file => { |
| 684 | // 优先使用文件自身的 file_type,如果没有则回退到 item.file_type | 762 | // 优先使用文件自身的 file_type,如果没有则回退到 item.file_type |
| 685 | - const type = file.file_type || item.file_type; | 763 | + const type = file.file_type || item.file_type |
| 686 | 764 | ||
| 687 | if (type === 'image') { | 765 | if (type === 'image') { |
| 688 | - images.push(file.value); | 766 | + images.push(file.value) |
| 689 | } else if (type === 'video') { | 767 | } else if (type === 'video') { |
| 690 | videoList.push({ | 768 | videoList.push({ |
| 691 | id: file.meta_id, | 769 | id: file.meta_id, |
| 692 | video: file.value, | 770 | video: file.value, |
| 693 | videoCover: file.cover, | 771 | videoCover: file.cover, |
| 694 | isPlaying: false, | 772 | isPlaying: false, |
| 695 | - }); | 773 | + }) |
| 696 | } else if (type === 'audio') { | 774 | } else if (type === 'audio') { |
| 697 | audio.push({ | 775 | audio.push({ |
| 698 | title: file.name ? file.name : '打卡音频', | 776 | title: file.name ? file.name : '打卡音频', |
| 699 | artist: file.artist ? file.artist : '', | 777 | artist: file.artist ? file.artist : '', |
| 700 | url: file.value, | 778 | url: file.value, |
| 701 | cover: file.cover ? file.cover : '', | 779 | cover: file.cover ? file.cover : '', |
| 702 | - }); | 780 | + }) |
| 703 | } | 781 | } |
| 704 | - }); | 782 | + }) |
| 705 | } | 783 | } |
| 706 | 784 | ||
| 785 | + const feedback_list = Array.isArray(item.feedback_list) | ||
| 786 | + ? item.feedback_list.map((feedback, feedbackIndex) => { | ||
| 787 | + const score = Number(feedback.score ?? 0) | ||
| 788 | + | ||
| 789 | + return { | ||
| 790 | + id: feedback.id ?? `feedback-${item.id}-${feedbackIndex}`, | ||
| 791 | + time: feedback.created_time || '', | ||
| 792 | + note: feedback.note || '', | ||
| 793 | + score: Number.isNaN(score) ? 0 : score, | ||
| 794 | + } | ||
| 795 | + }) | ||
| 796 | + : [] | ||
| 797 | + | ||
| 707 | return { | 798 | return { |
| 708 | id: item.id, | 799 | id: item.id, |
| 709 | task_id: item.task_id, | 800 | task_id: item.task_id, |
| ... | @@ -726,9 +817,10 @@ const formatData = (data) => { | ... | @@ -726,9 +817,10 @@ const formatData = (data) => { |
| 726 | subtask_id: item.subtask_id, | 817 | subtask_id: item.subtask_id, |
| 727 | gratitude_count: item.gratitude_count, | 818 | gratitude_count: item.gratitude_count, |
| 728 | gratitude_form_list: item.gratitude_form_list, | 819 | gratitude_form_list: item.gratitude_form_list, |
| 820 | + feedback_list, | ||
| 729 | } | 821 | } |
| 730 | }) | 822 | }) |
| 731 | - return formattedData; | 823 | + return formattedData |
| 732 | } | 824 | } |
| 733 | 825 | ||
| 734 | // 记录上次的taskId,用于判断是否切换了任务 | 826 | // 记录上次的taskId,用于判断是否切换了任务 |
| ... | @@ -758,9 +850,9 @@ const get_calendar_offset = () => { | ... | @@ -758,9 +850,9 @@ const get_calendar_offset = () => { |
| 758 | * @description 是否开启滚动调试日志 | 850 | * @description 是否开启滚动调试日志 |
| 759 | * @returns {boolean} 是否输出日志 | 851 | * @returns {boolean} 是否输出日志 |
| 760 | */ | 852 | */ |
| 761 | -const is_scroll_log_enabled = () => { | 853 | +const is_scroll_log_enabled = () => |
| 762 | - return String(route.query.scroll_log || '') === '1' || sessionStorage.getItem('checkin_scroll_log') === '1' | 854 | + String(route.query.scroll_log || '') === '1' || |
| 763 | -} | 855 | + sessionStorage.getItem('checkin_scroll_log') === '1' |
| 764 | 856 | ||
| 765 | const { | 857 | const { |
| 766 | save_state: save_checkin_scroll_state, | 858 | save_state: save_checkin_scroll_state, |
| ... | @@ -805,20 +897,20 @@ onActivated(async () => { | ... | @@ -805,20 +897,20 @@ onActivated(async () => { |
| 805 | const lastId = String(lastTaskId.value || '') | 897 | const lastId = String(lastTaskId.value || '') |
| 806 | 898 | ||
| 807 | if (currentId && currentId !== lastId) { | 899 | if (currentId && currentId !== lastId) { |
| 808 | - lastTaskId.value = currentId; | 900 | + lastTaskId.value = currentId |
| 809 | // 如果任务ID变化,强制刷新整个页面数据 | 901 | // 如果任务ID变化,强制刷新整个页面数据 |
| 810 | - await initPage(route.query.date); | 902 | + await initPage(route.query.date) |
| 811 | // 滚动到顶部 | 903 | // 滚动到顶部 |
| 812 | clear_checkin_scroll_state() | 904 | clear_checkin_scroll_state() |
| 813 | const scroll_el = resolve_checkin_scroll_el() | 905 | const scroll_el = resolve_checkin_scroll_el() |
| 814 | if (scroll_el === window) { | 906 | if (scroll_el === window) { |
| 815 | - window.scrollTo(0, 0); | 907 | + window.scrollTo(0, 0) |
| 816 | } else if (scroll_el && typeof scroll_el.scrollTo === 'function') { | 908 | } else if (scroll_el && typeof scroll_el.scrollTo === 'function') { |
| 817 | - scroll_el.scrollTo({ top: 0, left: 0, behavior: 'auto' }); | 909 | + scroll_el.scrollTo({ top: 0, left: 0, behavior: 'auto' }) |
| 818 | } else if (scroll_el) { | 910 | } else if (scroll_el) { |
| 819 | - scroll_el.scrollTop = 0; | 911 | + scroll_el.scrollTop = 0 |
| 820 | } | 912 | } |
| 821 | - return; | 913 | + return |
| 822 | } | 914 | } |
| 823 | 915 | ||
| 824 | // 检查是否有数据刷新标记 | 916 | // 检查是否有数据刷新标记 |
| ... | @@ -845,20 +937,20 @@ onActivated(async () => { | ... | @@ -845,20 +937,20 @@ onActivated(async () => { |
| 845 | 937 | ||
| 846 | if (refreshType === 'edit') { | 938 | if (refreshType === 'edit') { |
| 847 | // 编辑模式:更新列表中的对应项 | 939 | // 编辑模式:更新列表中的对应项 |
| 848 | - const index = checkinDataList.value.findIndex(item => item.id == refreshId) | 940 | + const index = checkinDataList.value.findIndex(item => item.id === refreshId) |
| 849 | if (index > -1) { | 941 | if (index > -1) { |
| 850 | checkinDataList.value.splice(index, 1, formattedItem) | 942 | checkinDataList.value.splice(index, 1, formattedItem) |
| 851 | } | 943 | } |
| 852 | has_refreshed = true | 944 | has_refreshed = true |
| 853 | } else if (refreshType === 'add') { | 945 | } else if (refreshType === 'add') { |
| 854 | // 新增模式:添加到列表顶部,且去重 | 946 | // 新增模式:添加到列表顶部,且去重 |
| 855 | - const exists = checkinDataList.value.some(item => item.id == formattedItem.id) | 947 | + const exists = checkinDataList.value.some(item => item.id === formattedItem.id) |
| 856 | if (!exists) { | 948 | if (!exists) { |
| 857 | checkinDataList.value.unshift(formattedItem) | 949 | checkinDataList.value.unshift(formattedItem) |
| 858 | } | 950 | } |
| 859 | // 更新统计数据 | 951 | // 更新统计数据 |
| 860 | - const current_date = route.query.date || dayjs().format('YYYY-MM-DD'); | 952 | + const current_date = route.query.date || dayjs().format('YYYY-MM-DD') |
| 861 | - getTaskDetail(dayjs(current_date).format('YYYY-MM')); | 953 | + getTaskDetail(dayjs(current_date).format('YYYY-MM')) |
| 862 | has_refreshed = true | 954 | has_refreshed = true |
| 863 | } | 955 | } |
| 864 | } | 956 | } |
| ... | @@ -886,14 +978,14 @@ onActivated(async () => { | ... | @@ -886,14 +978,14 @@ onActivated(async () => { |
| 886 | * @param {string|number} anchor_id - 打卡动态ID | 978 | * @param {string|number} anchor_id - 打卡动态ID |
| 887 | * @returns {Element|null} DOM 元素 | 979 | * @returns {Element|null} DOM 元素 |
| 888 | */ | 980 | */ |
| 889 | - const resolve_anchor_el = (anchor_id) => { | 981 | + const resolve_anchor_el = anchor_id => { |
| 890 | const card = checkinCardRefs.value.get(anchor_id) | 982 | const card = checkinCardRefs.value.get(anchor_id) |
| 891 | return card?.$el || card?.$?.vnode?.el || null | 983 | return card?.$el || card?.$?.vnode?.el || null |
| 892 | } | 984 | } |
| 893 | 985 | ||
| 894 | // 注释:优先尝试“从详情页返回”的滚动恢复;普通刷新不会走 should_restore | 986 | // 注释:优先尝试“从详情页返回”的滚动恢复;普通刷新不会走 should_restore |
| 895 | const restored_state = await restore_checkin_scroll_state({ | 987 | const restored_state = await restore_checkin_scroll_state({ |
| 896 | - wait_for: (state) => { | 988 | + wait_for: state => { |
| 897 | const wrapper_height = fixedCalendarWrapper.value?.getBoundingClientRect?.().height || 0 | 989 | const wrapper_height = fixedCalendarWrapper.value?.getBoundingClientRect?.().height || 0 |
| 898 | const height = Number(calendarHeight.value || 0) | 990 | const height = Number(calendarHeight.value || 0) |
| 899 | if (wrapper_height <= 0 || height <= 0) return false | 991 | if (wrapper_height <= 0 || height <= 0) return false |
| ... | @@ -915,9 +1007,11 @@ onActivated(async () => { | ... | @@ -915,9 +1007,11 @@ onActivated(async () => { |
| 915 | const anchor_el = resolve_anchor_el(state.anchor_id) | 1007 | const anchor_el = resolve_anchor_el(state.anchor_id) |
| 916 | if (!anchor_el || typeof anchor_el.getBoundingClientRect !== 'function') return null | 1008 | if (!anchor_el || typeof anchor_el.getBoundingClientRect !== 'function') return null |
| 917 | 1009 | ||
| 918 | - const scroll_rect_top = scroll_el === window ? 0 : (scroll_el?.getBoundingClientRect?.().top || 0) | 1010 | + const scroll_rect_top = |
| 1011 | + scroll_el === window ? 0 : scroll_el?.getBoundingClientRect?.().top || 0 | ||
| 919 | const anchor_rect = anchor_el.getBoundingClientRect() | 1012 | const anchor_rect = anchor_el.getBoundingClientRect() |
| 920 | - const current_scroll_top = scroll_el === window ? (window.scrollY || 0) : (scroll_el?.scrollTop || 0) | 1013 | + const current_scroll_top = |
| 1014 | + scroll_el === window ? window.scrollY || 0 : scroll_el?.scrollTop || 0 | ||
| 921 | const base_top = anchor_rect.top - scroll_rect_top + current_scroll_top | 1015 | const base_top = anchor_rect.top - scroll_rect_top + current_scroll_top |
| 922 | return Math.max(0, base_top - current_calendar_height - 10) | 1016 | return Math.max(0, base_top - current_calendar_height - 10) |
| 923 | } | 1017 | } |
| ... | @@ -941,16 +1035,21 @@ onActivated(async () => { | ... | @@ -941,16 +1035,21 @@ onActivated(async () => { |
| 941 | if (!restored_state && is_checkin_page_reload()) { | 1035 | if (!restored_state && is_checkin_page_reload()) { |
| 942 | log_scroll('触发刷新兜底逻辑:准备强制滚动') | 1036 | log_scroll('触发刷新兜底逻辑:准备强制滚动') |
| 943 | await nextTick() | 1037 | await nextTick() |
| 944 | - await wait_scroll_ready(() => { | 1038 | + await wait_scroll_ready( |
| 1039 | + () => { | ||
| 945 | const wrapper_height = fixedCalendarWrapper.value?.getBoundingClientRect?.().height || 0 | 1040 | const wrapper_height = fixedCalendarWrapper.value?.getBoundingClientRect?.().height || 0 |
| 946 | const height = Number(calendarHeight.value || 0) | 1041 | const height = Number(calendarHeight.value || 0) |
| 947 | if (wrapper_height <= 0 || height <= 0) return false | 1042 | if (wrapper_height <= 0 || height <= 0) return false |
| 948 | return Math.abs(wrapper_height - height) <= 2 | 1043 | return Math.abs(wrapper_height - height) <= 2 |
| 949 | - }, { timeout_ms: 1500, interval_ms: 16 }) | 1044 | + }, |
| 1045 | + { timeout_ms: 1500, interval_ms: 16 } | ||
| 1046 | + ) | ||
| 950 | const forced_result = await force_scroll_to(0, { times: 3, settle_frames: 1, behavior: 'auto' }) | 1047 | const forced_result = await force_scroll_to(0, { times: 3, settle_frames: 1, behavior: 'auto' }) |
| 951 | log_scroll('刷新兜底逻辑结束', { forced_top: true, ...forced_result }) | 1048 | log_scroll('刷新兜底逻辑结束', { forced_top: true, ...forced_result }) |
| 952 | } else if (!restored_state) { | 1049 | } else if (!restored_state) { |
| 953 | - log_scroll('未恢复且不满足刷新兜底条件(不会强制滚动)', { is_reload: is_checkin_page_reload() }) | 1050 | + log_scroll('未恢复且不满足刷新兜底条件(不会强制滚动)', { |
| 1051 | + is_reload: is_checkin_page_reload(), | ||
| 1052 | + }) | ||
| 954 | } | 1053 | } |
| 955 | }) | 1054 | }) |
| 956 | </script> | 1055 | </script> |
| ... | @@ -963,7 +1062,12 @@ onActivated(async () => { | ... | @@ -963,7 +1062,12 @@ onActivated(async () => { |
| 963 | left: 0; | 1062 | left: 0; |
| 964 | right: 0; | 1063 | right: 0; |
| 965 | z-index: 1000; | 1064 | z-index: 1000; |
| 966 | - background: linear-gradient(to bottom right, #f0fdf4, #f0fdfa, #eff6ff); // 与AppLayout保持一致的渐变背景 | 1065 | + background: linear-gradient( |
| 1066 | + to bottom right, | ||
| 1067 | + #f0fdf4, | ||
| 1068 | + #f0fdfa, | ||
| 1069 | + #eff6ff | ||
| 1070 | + ); // 与AppLayout保持一致的渐变背景 | ||
| 967 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | 1071 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| 968 | } | 1072 | } |
| 969 | 1073 | ||
| ... | @@ -993,7 +1097,7 @@ onActivated(async () => { | ... | @@ -993,7 +1097,7 @@ onActivated(async () => { |
| 993 | 1097 | ||
| 994 | .calendar-fill-checkin { | 1098 | .calendar-fill-checkin { |
| 995 | .van-calendar__selected-day { | 1099 | .van-calendar__selected-day { |
| 996 | - background: #FFF !important; | 1100 | + background: #fff !important; |
| 997 | border: 1px solid darkgoldenrod !important; | 1101 | border: 1px solid darkgoldenrod !important; |
| 998 | color: darkgoldenrod !important; | 1102 | color: darkgoldenrod !important; |
| 999 | } | 1103 | } |
| ... | @@ -1001,7 +1105,7 @@ onActivated(async () => { | ... | @@ -1001,7 +1105,7 @@ onActivated(async () => { |
| 1001 | 1105 | ||
| 1002 | .calendar-today { | 1106 | .calendar-today { |
| 1003 | .van-calendar__selected-day { | 1107 | .van-calendar__selected-day { |
| 1004 | - background: #FAAB0C !important; | 1108 | + background: #faab0c !important; |
| 1005 | } | 1109 | } |
| 1006 | } | 1110 | } |
| 1007 | 1111 | ... | ... |
-
Please register or login to post a comment