refactor(composables): 提取图片加载和错误处理逻辑到可复用模块
将分散在各组件的图片加载错误处理函数提取到 useImageLoader composable 将错误处理逻辑提取到 useErrorHandler composable 更新 ISSUES_TO_FIX.md 记录已修复组件清单
Showing
6 changed files
with
54 additions
and
30 deletions
| ... | @@ -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 | ... | ... |
-
Please register or login to post a comment