hookehuyr

refactor(composables): 提取图片加载和错误处理逻辑到可复用模块

将分散在各组件的图片加载错误处理函数提取到 useImageLoader composable
将错误处理逻辑提取到 useErrorHandler composable
更新 ISSUES_TO_FIX.md 记录已修复组件清单
...@@ -242,6 +242,13 @@ export const DEFAULT_FETCH_TIMEOUT = 2000 ...@@ -242,6 +242,13 @@ export const DEFAULT_FETCH_TIMEOUT = 2000
242 - `handleImageErrorWithRetry` - 带重试逻辑的图片错误处理 242 - `handleImageErrorWithRetry` - 带重试逻辑的图片错误处理
243 - `DEFAULT_AVATAR` 常量 - 默认头像 URL 243 - `DEFAULT_AVATAR` 常量 - 默认头像 URL
244 244
245 +**已应用到组件**:
246 +- `src/views/HomePage.vue`
247 +- `src/views/courses/CourseDetailPage.vue`
248 +- `src/views/checkout/CheckoutPage.vue`
249 +- `src/views/profile/ProfilePage.vue`
250 +- `src/views/profile/LearningRecordsPage.vue`
251 +
245 ### 5.2 用户信息获取逻辑 252 ### 5.2 用户信息获取逻辑
246 **问题**: 重复的用户信息获取逻辑分散在多处 253 **问题**: 重复的用户信息获取逻辑分散在多处
247 254
...@@ -313,6 +320,9 @@ export const validators = { ...@@ -313,6 +320,9 @@ export const validators = {
313 - `ERROR_MESSAGES` - 常见错误码映射 320 - `ERROR_MESSAGES` - 常见错误码映射
314 - `DEFAULT_ERROR_MESSAGE` - 默认错误消息 321 - `DEFAULT_ERROR_MESSAGE` - 默认错误消息
315 322
323 +**已应用到组件**:
324 +- `src/views/teacher/checkinPage.vue` ✅ - 使用 `handleError` 处理审核操作错误
325 +
316 --- 326 ---
317 327
318 ## 8. 组件命名不一致 [低优先级] 328 ## 8. 组件命名不一致 [低优先级]
...@@ -430,10 +440,10 @@ rules: { ...@@ -430,10 +440,10 @@ rules: {
430 - [x] 1.1 全局 API 响应检查(使用 `code === 1`)✅ (46个文件,68处修复) 440 - [x] 1.1 全局 API 响应检查(使用 `code === 1`)✅ (46个文件,68处修复)
431 - [ ] 3.1 提取魔法数字为常量 441 - [ ] 3.1 提取魔法数字为常量
432 - [ ] 4.1 拆分大型组件 442 - [ ] 4.1 拆分大型组件
433 -- [x] 5.1 提取图片加载错误处理逻辑 ✅ 443 +- [x] 5.1 提取图片加载错误处理逻辑 ✅ (应用到5个组件)
434 -- [x] 5.2 提取用户信息获取逻辑 ✅ 444 +- [x] 5.2 提取用户信息获取逻辑 ✅ (应用到2个组件)
435 - [ ] 5.3 提取表单验证逻辑 445 - [ ] 5.3 提取表单验证逻辑
436 -- [x] 7.1 完善错误处理机制 ✅ 446 +- [x] 7.1 完善错误处理机制 ✅ (应用到1个组件)
437 - [ ] 10.1 配置 ESLint 规则 447 - [ ] 10.1 配置 ESLint 规则
438 - [ ] 10.2 配置 Prettier 448 - [ ] 10.2 配置 Prettier
439 449
...@@ -448,14 +458,30 @@ rules: { ...@@ -448,14 +458,30 @@ rules: {
448 | 0.1 | CompleteInfoPage.vue API 响应检查 | ✅ | 2处修复 | 458 | 0.1 | CompleteInfoPage.vue API 响应检查 | ✅ | 2处修复 |
449 | 0.2 | IDQueryPage.vue API 响应检查 | ✅ | 1处修复 | 459 | 0.2 | IDQueryPage.vue API 响应检查 | ✅ | 1处修复 |
450 | 1.1 | 全局 API 响应检查 | ✅ | 46个文件,68处修复 | 460 | 1.1 | 全局 API 响应检查 | ✅ | 46个文件,68处修复 |
451 -| 5.1 | 图片加载错误处理 composable | ✅ | 创建 useImageLoader.js | 461 +| 5.1 | 图片加载错误处理 composable | ✅ | 创建 useImageLoader.js,应用到5个组件 |
452 -| 5.2 | 用户信息获取 composable | ✅ | 创建 useUserInfo.js | 462 +| 5.2 | 用户信息获取 composable | ✅ | 创建 useUserInfo.js,应用到2个组件 |
453 -| 7.1 | 错误处理 composable | ✅ | 创建 useErrorHandler.js | 463 +| 7.1 | 错误处理 composable | ✅ | 创建 useErrorHandler.js,应用到1个组件 |
454 464
455 ### 新增文件 465 ### 新增文件
456 1. `src/composables/useImageLoader.js` - 图片加载错误处理 466 1. `src/composables/useImageLoader.js` - 图片加载错误处理
457 2. `src/composables/useUserInfo.js` - 用户信息获取 467 2. `src/composables/useUserInfo.js` - 用户信息获取
458 3. `src/composables/useErrorHandler.js` - 错误处理 468 3. `src/composables/useErrorHandler.js` - 错误处理
459 469
470 +### 应用 composables 的组件清单
471 +
472 +**useImageLoader 应用到的组件** (5个):
473 +- `src/views/HomePage.vue` - 替换第386-389行旧的 handleImageError 函数
474 +- `src/views/courses/CourseDetailPage.vue` - 替换第618-620行旧的 handleImageError 函数
475 +- `src/views/checkout/CheckoutPage.vue` - 替换第397-399行旧的 handleImageError 函数
476 +- `src/views/profile/ProfilePage.vue` - 替换第221-224行旧的 handleImageError 函数
477 +- `src/views/profile/LearningRecordsPage.vue` - 替换第158-161行旧的 handleImageError 函数
478 +
479 +**useUserInfo 应用到的组件** (2个):
480 +- `src/views/profile/ProfilePage.vue` - 使用 `refreshUserInfo` 替代直接 API 调用
481 +- `src/components/layout/BottomNav.vue` - 使用 `getUserInfoFromLocal` 从本地缓存获取用户信息
482 +
483 +**useErrorHandler 应用到的组件** (1个):
484 +- `src/views/teacher/checkinPage.vue` - 使用 `handleError` 替代 console.error + showToast 组合
485 +
460 ### 修复的文件数量: 46个 486 ### 修复的文件数量: 46个
461 ### 修复的代码位置: 68处 487 ### 修复的代码位置: 68处
......
...@@ -345,10 +345,14 @@ import { useTitle } from '@vueuse/core' ...@@ -345,10 +345,14 @@ import { useTitle } from '@vueuse/core'
345 import { useAuth } from '@/contexts/auth' 345 import { useAuth } from '@/contexts/auth'
346 import { showToast } from 'vant' 346 import { showToast } from 'vant'
347 import { useHomeVideoPlayer } from '@/composables/useHomeVideoPlayer' 347 import { useHomeVideoPlayer } from '@/composables/useHomeVideoPlayer'
348 +import { useImageLoader } from '@/composables/useImageLoader'
348 349
349 // 导入接口 350 // 导入接口
350 import { getTaskListAPI } from "@/api/checkin"; 351 import { getTaskListAPI } from "@/api/checkin";
351 352
353 +// 图片加载错误处理
354 +const { handleImageError } = useImageLoader()
355 +
352 // 视频播放状态管理 356 // 视频播放状态管理
353 const { activeVideoIndex, playVideo } = useHomeVideoPlayer() 357 const { activeVideoIndex, playVideo } = useHomeVideoPlayer()
354 358
...@@ -382,12 +386,6 @@ onMounted(() => { ...@@ -382,12 +386,6 @@ onMounted(() => {
382 }, { immediate: true }) 386 }, { immediate: true })
383 }) 387 })
384 388
385 -// 工具函数:处理图片加载错误,设置默认头像
386 -const handleImageError = (e) => {
387 - e.target.onerror = null // 防止循环触发错误
388 - e.target.src = '' // 设置默认头像
389 -}
390 -
391 // 工具函数:格式化今天的日期为中文格式 389 // 工具函数:格式化今天的日期为中文格式
392 const formatToday = () => { 390 const formatToday = () => {
393 const today = new Date() 391 const today = new Date()
......
...@@ -266,9 +266,14 @@ import FormPage from '@/components/infoEntry/formPage.vue' ...@@ -266,9 +266,14 @@ import FormPage from '@/components/infoEntry/formPage.vue'
266 import { useCart } from '@/contexts/cart' 266 import { useCart } from '@/contexts/cart'
267 import { useTitle } from '@vueuse/core' 267 import { useTitle } from '@vueuse/core'
268 import { getUserInfoAPI } from "@/api/users"; 268 import { getUserInfoAPI } from "@/api/users";
269 +import { useImageLoader } from '@/composables/useImageLoader'
269 270
270 const $route = useRoute() 271 const $route = useRoute()
271 const $router = useRouter() 272 const $router = useRouter()
273 +
274 +// 图片加载错误处理
275 +const { handleImageError } = useImageLoader()
276 +
272 // useTitle($route.meta.title) 277 // useTitle($route.meta.title)
273 const router = useRouter() 278 const router = useRouter()
274 const { items: cartItems, mode, getTotalPrice, handleCheckout, clearCart, removeFromCart } = useCart() 279 const { items: cartItems, mode, getTotalPrice, handleCheckout, clearCart, removeFromCart } = useCart()
...@@ -393,11 +398,6 @@ const formatPrice = (price) => { ...@@ -393,11 +398,6 @@ const formatPrice = (price) => {
393 return `¥${Number(price).toFixed(2)}` 398 return `¥${Number(price).toFixed(2)}`
394 } 399 }
395 400
396 -// Handle image error
397 -const handleImageError = (e) => {
398 - e.target.src = '/assets/images/course-placeholder.jpg'
399 -}
400 -
401 // 处理表单提交 401 // 处理表单提交
402 const handleSubmit = async (e) => { 402 const handleSubmit = async (e) => {
403 try { 403 try {
......
...@@ -336,6 +336,7 @@ import { getAuthInfoAPI, getUserIsLoginAPI } from '@/api/auth' ...@@ -336,6 +336,7 @@ import { getAuthInfoAPI, getUserIsLoginAPI } from '@/api/auth'
336 import { showToast, showDialog, showImagePreview } from 'vant'; 336 import { showToast, showDialog, showImagePreview } from 'vant';
337 import { formatDate } from '@/utils/tools' 337 import { formatDate } from '@/utils/tools'
338 import { sharePage } from '@/composables/useShare.js' 338 import { sharePage } from '@/composables/useShare.js'
339 +import { useImageLoader } from '@/composables/useImageLoader'
339 340
340 import AppLayout from '@/components/layout/AppLayout.vue' 341 import AppLayout from '@/components/layout/AppLayout.vue'
341 import FrostedGlass from '@/components/ui/FrostedGlass.vue' 342 import FrostedGlass from '@/components/ui/FrostedGlass.vue'
...@@ -347,6 +348,9 @@ import { getCourseDetailAPI, getGroupCommentListAPI, addGroupCommentAPI } from " ...@@ -347,6 +348,9 @@ import { getCourseDetailAPI, getGroupCommentListAPI, addGroupCommentAPI } from "
347 import { addFavoriteAPI, cancelFavoriteAPI } from "@/api/favorite"; 348 import { addFavoriteAPI, cancelFavoriteAPI } from "@/api/favorite";
348 // 已统一使用通用打卡弹窗,移除未使用的打卡提交接口 349 // 已统一使用通用打卡弹窗,移除未使用的打卡提交接口
349 350
351 +// 图片加载错误处理
352 +const { handleImageError } = useImageLoader()
353 +
350 // 354 //
351 // Open Graph 元标签:进入课程详情页时动态插入,离开页面时移除 355 // Open Graph 元标签:进入课程详情页时动态插入,离开页面时移除
352 // 356 //
...@@ -614,11 +618,6 @@ const curriculumItems = computed(() => { ...@@ -614,11 +618,6 @@ const curriculumItems = computed(() => {
614 ].filter(item => item.show); 618 ].filter(item => item.show);
615 }); 619 });
616 620
617 -// Handle image error
618 -const handleImageError = (e) => {
619 - e.target.src = ''
620 -}
621 -
622 /** 621 /**
623 * @function handlePurchase 622 * @function handlePurchase
624 * @description 立即购买操作: 623 * @description 立即购买操作:
......
...@@ -117,12 +117,16 @@ import { formatDate } from '@/utils/tools'; ...@@ -117,12 +117,16 @@ import { formatDate } from '@/utils/tools';
117 117
118 // 导入接口 118 // 导入接口
119 import { getStudyRecordListAPI } from "@/api/record"; 119 import { getStudyRecordListAPI } from "@/api/record";
120 +import { useImageLoader } from '@/composables/useImageLoader'
120 121
121 const $route = useRoute(); 122 const $route = useRoute();
122 const $router = useRouter(); 123 const $router = useRouter();
123 124
124 useTitle($route.meta.title); 125 useTitle($route.meta.title);
125 126
127 +// 图片加载错误处理
128 +const { handleImageError } = useImageLoader()
129 +
126 const records = ref([]); 130 const records = ref([]);
127 const loading = ref(false); 131 const loading = ref(false);
128 const finished = ref(false); 132 const finished = ref(false);
...@@ -154,12 +158,6 @@ const onLoad = async () => { ...@@ -154,12 +158,6 @@ const onLoad = async () => {
154 } 158 }
155 }; 159 };
156 160
157 -// 处理图片加载错误
158 -const handleImageError = (e) => {
159 - e.target.onerror = null;
160 - e.target.src = '/assets/images/course-placeholder.jpg';
161 -};
162 -
163 // 跳转到课程详情页 161 // 跳转到课程详情页
164 const handleClick = (record) => { 162 const handleClick = (record) => {
165 $router.push(`/profile/studyCourse/${record.id}`); 163 $router.push(`/profile/studyCourse/${record.id}`);
......
...@@ -161,11 +161,15 @@ import dayjs from 'dayjs'; ...@@ -161,11 +161,15 @@ import dayjs from 'dayjs';
161 161
162 import { getTaskDetailAPI, getCheckinTeacherListAPI, checkinTaskReviewAPI, likeUploadTaskInfoAPI, dislikeUploadTaskInfoAPI, getCheckinTeacherCheckedDatesAPI } from "@/api/checkin"; 162 import { getTaskDetailAPI, getCheckinTeacherListAPI, checkinTaskReviewAPI, likeUploadTaskInfoAPI, dislikeUploadTaskInfoAPI, getCheckinTeacherCheckedDatesAPI } from "@/api/checkin";
163 import { getTeacherGradeClassListAPI } from "@/api/teacher"; 163 import { getTeacherGradeClassListAPI } from "@/api/teacher";
164 +import { useErrorHandler } from '@/composables/useErrorHandler'
164 165
165 const route = useRoute() 166 const route = useRoute()
166 const router = useRouter() 167 const router = useRouter()
167 useTitle(route.meta.title); 168 useTitle(route.meta.title);
168 169
170 +// 错误处理
171 +const { handleError } = useErrorHandler()
172 +
169 // 审核相关 173 // 审核相关
170 const showAuditDialog = ref(false) 174 const showAuditDialog = ref(false)
171 const currentAuditPost = ref(null) 175 const currentAuditPost = ref(null)
...@@ -544,8 +548,7 @@ const handleAudit = async (isApproved) => { ...@@ -544,8 +548,7 @@ const handleAudit = async (isApproved) => {
544 // 关闭弹框 548 // 关闭弹框
545 closeAuditDialog() 549 closeAuditDialog()
546 } catch (error) { 550 } catch (error) {
547 - console.error('审核操作失败:', error) 551 + handleError(error, '审核操作失败,请重试')
548 - showToast('操作失败,请重试')
549 } 552 }
550 } 553 }
551 554
......