hookehuyr

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

将分散在各组件的图片加载错误处理函数提取到 useImageLoader composable
将错误处理逻辑提取到 useErrorHandler composable
更新 ISSUES_TO_FIX.md 记录已修复组件清单
......@@ -242,6 +242,13 @@ export const DEFAULT_FETCH_TIMEOUT = 2000
- `handleImageErrorWithRetry` - 带重试逻辑的图片错误处理
- `DEFAULT_AVATAR` 常量 - 默认头像 URL
**已应用到组件**:
- `src/views/HomePage.vue`
- `src/views/courses/CourseDetailPage.vue`
- `src/views/checkout/CheckoutPage.vue`
- `src/views/profile/ProfilePage.vue`
- `src/views/profile/LearningRecordsPage.vue`
### 5.2 用户信息获取逻辑
**问题**: 重复的用户信息获取逻辑分散在多处
......@@ -313,6 +320,9 @@ export const validators = {
- `ERROR_MESSAGES` - 常见错误码映射
- `DEFAULT_ERROR_MESSAGE` - 默认错误消息
**已应用到组件**:
- `src/views/teacher/checkinPage.vue` ✅ - 使用 `handleError` 处理审核操作错误
---
## 8. 组件命名不一致 [低优先级]
......@@ -430,10 +440,10 @@ rules: {
- [x] 1.1 全局 API 响应检查(使用 `code === 1`)✅ (46个文件,68处修复)
- [ ] 3.1 提取魔法数字为常量
- [ ] 4.1 拆分大型组件
- [x] 5.1 提取图片加载错误处理逻辑 ✅
- [x] 5.2 提取用户信息获取逻辑 ✅
- [x] 5.1 提取图片加载错误处理逻辑 ✅ (应用到5个组件)
- [x] 5.2 提取用户信息获取逻辑 ✅ (应用到2个组件)
- [ ] 5.3 提取表单验证逻辑
- [x] 7.1 完善错误处理机制 ✅
- [x] 7.1 完善错误处理机制 ✅ (应用到1个组件)
- [ ] 10.1 配置 ESLint 规则
- [ ] 10.2 配置 Prettier
......@@ -448,14 +458,30 @@ rules: {
| 0.1 | CompleteInfoPage.vue API 响应检查 | ✅ | 2处修复 |
| 0.2 | IDQueryPage.vue API 响应检查 | ✅ | 1处修复 |
| 1.1 | 全局 API 响应检查 | ✅ | 46个文件,68处修复 |
| 5.1 | 图片加载错误处理 composable | ✅ | 创建 useImageLoader.js |
| 5.2 | 用户信息获取 composable | ✅ | 创建 useUserInfo.js |
| 7.1 | 错误处理 composable | ✅ | 创建 useErrorHandler.js |
| 5.1 | 图片加载错误处理 composable | ✅ | 创建 useImageLoader.js,应用到5个组件 |
| 5.2 | 用户信息获取 composable | ✅ | 创建 useUserInfo.js,应用到2个组件 |
| 7.1 | 错误处理 composable | ✅ | 创建 useErrorHandler.js,应用到1个组件 |
### 新增文件
1. `src/composables/useImageLoader.js` - 图片加载错误处理
2. `src/composables/useUserInfo.js` - 用户信息获取
3. `src/composables/useErrorHandler.js` - 错误处理
### 应用 composables 的组件清单
**useImageLoader 应用到的组件** (5个):
- `src/views/HomePage.vue` - 替换第386-389行旧的 handleImageError 函数
- `src/views/courses/CourseDetailPage.vue` - 替换第618-620行旧的 handleImageError 函数
- `src/views/checkout/CheckoutPage.vue` - 替换第397-399行旧的 handleImageError 函数
- `src/views/profile/ProfilePage.vue` - 替换第221-224行旧的 handleImageError 函数
- `src/views/profile/LearningRecordsPage.vue` - 替换第158-161行旧的 handleImageError 函数
**useUserInfo 应用到的组件** (2个):
- `src/views/profile/ProfilePage.vue` - 使用 `refreshUserInfo` 替代直接 API 调用
- `src/components/layout/BottomNav.vue` - 使用 `getUserInfoFromLocal` 从本地缓存获取用户信息
**useErrorHandler 应用到的组件** (1个):
- `src/views/teacher/checkinPage.vue` - 使用 `handleError` 替代 console.error + showToast 组合
### 修复的文件数量: 46个
### 修复的代码位置: 68处
......
......@@ -345,10 +345,14 @@ import { useTitle } from '@vueuse/core'
import { useAuth } from '@/contexts/auth'
import { showToast } from 'vant'
import { useHomeVideoPlayer } from '@/composables/useHomeVideoPlayer'
import { useImageLoader } from '@/composables/useImageLoader'
// 导入接口
import { getTaskListAPI } from "@/api/checkin";
// 图片加载错误处理
const { handleImageError } = useImageLoader()
// 视频播放状态管理
const { activeVideoIndex, playVideo } = useHomeVideoPlayer()
......@@ -382,12 +386,6 @@ onMounted(() => {
}, { immediate: true })
})
// 工具函数:处理图片加载错误,设置默认头像
const handleImageError = (e) => {
e.target.onerror = null // 防止循环触发错误
e.target.src = '' // 设置默认头像
}
// 工具函数:格式化今天的日期为中文格式
const formatToday = () => {
const today = new Date()
......
......@@ -266,9 +266,14 @@ import FormPage from '@/components/infoEntry/formPage.vue'
import { useCart } from '@/contexts/cart'
import { useTitle } from '@vueuse/core'
import { getUserInfoAPI } from "@/api/users";
import { useImageLoader } from '@/composables/useImageLoader'
const $route = useRoute()
const $router = useRouter()
// 图片加载错误处理
const { handleImageError } = useImageLoader()
// useTitle($route.meta.title)
const router = useRouter()
const { items: cartItems, mode, getTotalPrice, handleCheckout, clearCart, removeFromCart } = useCart()
......@@ -393,11 +398,6 @@ const formatPrice = (price) => {
return `¥${Number(price).toFixed(2)}`
}
// Handle image error
const handleImageError = (e) => {
e.target.src = '/assets/images/course-placeholder.jpg'
}
// 处理表单提交
const handleSubmit = async (e) => {
try {
......
......@@ -336,6 +336,7 @@ import { getAuthInfoAPI, getUserIsLoginAPI } from '@/api/auth'
import { showToast, showDialog, showImagePreview } from 'vant';
import { formatDate } from '@/utils/tools'
import { sharePage } from '@/composables/useShare.js'
import { useImageLoader } from '@/composables/useImageLoader'
import AppLayout from '@/components/layout/AppLayout.vue'
import FrostedGlass from '@/components/ui/FrostedGlass.vue'
......@@ -347,6 +348,9 @@ import { getCourseDetailAPI, getGroupCommentListAPI, addGroupCommentAPI } from "
import { addFavoriteAPI, cancelFavoriteAPI } from "@/api/favorite";
// 已统一使用通用打卡弹窗,移除未使用的打卡提交接口
// 图片加载错误处理
const { handleImageError } = useImageLoader()
//
// Open Graph 元标签:进入课程详情页时动态插入,离开页面时移除
//
......@@ -614,11 +618,6 @@ const curriculumItems = computed(() => {
].filter(item => item.show);
});
// Handle image error
const handleImageError = (e) => {
e.target.src = ''
}
/**
* @function handlePurchase
* @description 立即购买操作:
......
......@@ -117,12 +117,16 @@ import { formatDate } from '@/utils/tools';
// 导入接口
import { getStudyRecordListAPI } from "@/api/record";
import { useImageLoader } from '@/composables/useImageLoader'
const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);
// 图片加载错误处理
const { handleImageError } = useImageLoader()
const records = ref([]);
const loading = ref(false);
const finished = ref(false);
......@@ -154,12 +158,6 @@ const onLoad = async () => {
}
};
// 处理图片加载错误
const handleImageError = (e) => {
e.target.onerror = null;
e.target.src = '/assets/images/course-placeholder.jpg';
};
// 跳转到课程详情页
const handleClick = (record) => {
$router.push(`/profile/studyCourse/${record.id}`);
......
......@@ -161,11 +161,15 @@ import dayjs from 'dayjs';
import { getTaskDetailAPI, getCheckinTeacherListAPI, checkinTaskReviewAPI, likeUploadTaskInfoAPI, dislikeUploadTaskInfoAPI, getCheckinTeacherCheckedDatesAPI } from "@/api/checkin";
import { getTeacherGradeClassListAPI } from "@/api/teacher";
import { useErrorHandler } from '@/composables/useErrorHandler'
const route = useRoute()
const router = useRouter()
useTitle(route.meta.title);
// 错误处理
const { handleError } = useErrorHandler()
// 审核相关
const showAuditDialog = ref(false)
const currentAuditPost = ref(null)
......@@ -544,8 +548,7 @@ const handleAudit = async (isApproved) => {
// 关闭弹框
closeAuditDialog()
} catch (error) {
console.error('审核操作失败:', error)
showToast('操作失败,请重试')
handleError(error, '审核操作失败,请重试')
}
}
......