hookehuyr

fix(plan): 修复 PlanFormContainer prop 验证错误

修复了产品中心页面进入时的 Vue prop 类型验证警告:
- 明确指定 product prop 的 type 为 Object
- 将 required 设为 false 并允许 null 默认值
- 添加空值检查,防止访问 null.form_sn 报错
- 在 product-center 页面添加 v-if 条件渲染

影响范围:
- src/components/PlanFormContainer.vue
- src/pages/product-center/index.vue

相关文档:
- 更新 API 联调日志(搜索模块完成)
- 更新 CLAUDE.md(可复用组件说明)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
......@@ -155,6 +155,11 @@ go.back() // 返回上一页
**抽取原则**:"第 3 次出现原则" - 当相同代码模式出现 3 次时,**必须**抽取为 Composable。
**组件自包含原则**(新增):
- 对于重复的UI结构,抽取为可复用组件(如 MaterialCard、ProductCard)
- 组件应该自包含业务逻辑(查看、收藏等),通过事件与父组件通信
- 避免在父组件中重复编写相同的逻辑代码
### 6. 样式处理策略
**TailwindCSS vs Less 使用指南**
......@@ -212,7 +217,12 @@ src/pages/your-page/
- 热门产品的"产品资料"按钮跳转到 `product-detail` 页面,带产品 ID
- 热门资料的"查看更多"跳转到 `material-list` 页面
- 网格导航图标跳转到各个业务页面
- 使用可复用组件(MaterialCard、ProductCard)
2. `pages/search/index` - 产品和资料搜索页
- 支持实时搜索(输入关键字自动调用 searchAPI)
- 双Tab切换(产品、资料)
- 支持分页加载和触底加载更多
- 使用可复用组件(MaterialCard、ProductCard)
3. `pages/webview/index` - 外部 URL 的 WebView 包装器
4. `pages/document-preview/index` - 文档预览页
5. `pages/document-demo/index` - 文档预览演示页
......@@ -258,6 +268,8 @@ src/pages/your-page/
- `SectionCard.vue` - 分组卡片组件
- `SectionItem.vue` - 分组列表项组件
- `ListItemActions/` - 列表项操作按钮
- `MaterialCard.vue` - 资料卡片组件(可复用)
- `ProductCard.vue` - 产品卡片组件(可复用)
**表单与输入组件**
- `FilterTabs.vue` - 过滤标签组件
......@@ -536,6 +548,14 @@ store.setState('新值')
2. **`src/components/NavHeader.vue`** - 自定义导航头
3. **`src/components/SectionCard.vue`** - 分组卡片
4. **`src/components/DocumentPreview/`** - 文档预览
5. **`src/components/MaterialCard.vue`** - 资料卡片(可复用)
- 自包含业务逻辑:查看、收藏
- 支持动态标签、文件大小格式化、学习人数显示
- 使用页面:首页、搜索页、周热门资料页
6. **`src/components/ProductCard.vue`** - 产品卡片(可复用)
- 自定义样式:动态标签、封面图
- 支持产品详情查看和计划书功能
- 使用页面:首页、搜索页
### Composables
1. **`src/composables/useSectionList.js`** - 分组列表管理
......@@ -609,5 +629,10 @@ store.setState('新值')
- ✅ 遵循"第 3 次出现原则" - 代码重复 3 次时必须抽取
- ✅ 优先使用 Composables 而非 Mixins
- ✅ 组件职责单一,避免过度复杂
-**组件自包含业务逻辑** - 当UI结构重复出现时,抽取为可复用组件(如 MaterialCard、ProductCard)
- 组件内部处理业务逻辑(查看、收藏等)
- 通过事件与父组件通信
- 减少父组件的重复代码
- 示例:MaterialCard 组件在首页、搜索页、周热门资料页复用
[更多最佳实践详见经验教训总结](docs/lessons-learned.md)
......
......@@ -4,16 +4,27 @@
## 📊 总体进度
- **总接口数**: 26
- **已完成**: 14 (53.8%)
- **联调中**: 1 (3.8%)
- **已废弃**: 3 (11.5%)
- **待联调**: 8 (30.8%)
- **总接口数**: 27
- **已完成**: 15 (55.6%)
- **联调中**: 0 (0%)
- **已废弃**: 3 (11.1%)
- **待联调**: 9 (33.3%)
- **有阻塞**: 0
---
**📝 最近更新** (2026-02-05):
**📝 最近更新** (2026-02-06):
-**搜索模块联调完成**:searchAPI 接口前端已完成集成
- 支持产品和资料的实时搜索
- 支持分页加载和触底加载更多
- 使用可复用卡片组件(MaterialCard、ProductCard)
- 🆕 **新增可复用卡片组件**
- MaterialCard.vue - 资料卡片组件(272行)
- ProductCard.vue - 产品卡片组件(106行)
- 已应用到:搜索页、首页、周热门资料页
- 🔄 **页面重构**:搜索页、首页、周热门资料页使用新组件重构
- 减少代码重复:净减少238行
- 统一UI风格和交互逻辑
-**收藏模块联调完成**:2个接口(delAPI、listAPI)前端已完成联调
- 收藏列表API:获取收藏数据,支持分页
- 取消收藏API:删除单个收藏项
......@@ -957,6 +968,73 @@
---
### 搜索模块
#### 接口 1: 搜索(产品、资料)
**接口信息**
- **接口名称**: `searchAPI`
- **接口路径**: `/srv/?a=search`
- **请求方法**: GET
- **负责页面**: `src/pages/search/index.vue`
- **负责人**: 后端团队
**接口文档更新记录**
| 日期 | 版本 | 变更内容 | 变更原因 | 文档链接 |
|------|------|---------|---------|---------|
| 2026-02-06 | v1.0 | 初始版本 | 前端集成完成,联调成功 | [查看](docs/api-specs/search/search.md) |
**页面调试情况**
| 日期 | 调试页面 | 问题记录 | 解决方案 | 状态 |
|------|---------|---------|---------|------|
| 2026-02-06 | `src/pages/search/index.vue` | 无 | 实时搜索、分页加载均正常 | ✅ 已完成 |
**接口状态**: ✅ 已完成
**备注**:
- **参数**:
- `k`: 搜索关键字(必需)
- `type`: 搜索类型,product=产品,file=资料(必需)
- `page`: 页码,从 0 开始
- `limit`: 每页数量(默认 10)
- **返回数据结构**:
```javascript
{
code: 1,
data: {
list: [...], // 搜索结果列表
total: 100 // 总数
}
}
```
- **产品字段**:
- `id` - 产品ID
- `product_name` - 产品名称
- `tags[]` - 产品标签(含 bg_color/text_color)
- `cover_image` - 封面图
- `form_sn` - 计划书模板标识
- **资料字段**:
- `id` - 文件ID
- `title` - 文件标题
- `fileName` - 文件名
- `fileSize` - 文件大小
- `extension` - 文件扩展名
- `downloadUrl` - 下载链接
- `learners` - 学习人数
- `readPeoplePercent` - 学习人数比例
- `collected` - 是否已收藏
- **实现位置**: `src/pages/search/index.vue:204-265` (`performSearch` 函数)
- **功能特性**:
- 实时搜索:输入关键字后自动调用API
- 双Tab切换:支持产品和资料切换
- 分页加载:触底自动加载更多
- 结果统计:显示找到的相关结果数量
- 动画效果:列表项逐个淡入动画
---
### 埋点模块
#### 接口 1: 添加埋点
......@@ -1125,14 +1203,13 @@
---
**最后更新时间**: 2026-02-04 18:00
**文档版本**: v2.4
**最后更新时间**: 2026-02-06 17:20
**文档版本**: v2.5
**更新内容**:
- 📝 **文档模块接口字段确认**
- weekHotAPI(本周热门资料):字段已确认,更新接口文档
- fileListAPI(文档列表):字段已确认,更新接口文档,修正接口路径
- 更新总体进度:26个接口(12个已完成,11个待联调,3个已废弃)
- 文档模块从"初稿"状态更新为"待联调"状态
-**搜索模块联调完成**:新增 searchAPI 接口,前端已完成集成
- 🆕 **新增可复用卡片组件**:MaterialCard、ProductCard
- 🔄 **页面重构**:搜索页、首页、周热门资料页使用新组件重构,减少代码重复
- 更新总体进度:27个接口(15个已完成,9个待联调,3个已废弃)
**历史版本**:
- v2.1 (2026-02-03 21:00): 产品模块联调完成
......
......@@ -70,7 +70,8 @@ const props = defineProps({
*/
product: {
type: Object,
required: true
required: false,
default: null
}
})
......@@ -112,7 +113,11 @@ const emit = defineEmits([
* // }
*/
const templateConfig = computed(() => {
if (!props.product?.form_sn) {
if (!props.product) {
return null
}
if (!props.product.form_sn) {
console.warn('[PlanFormContainer] 产品缺少 form_sn 字段', props.product)
return null
}
......@@ -197,6 +202,11 @@ const close = () => {
* @description 将表单数据和产品信息一起提交
*/
const submit = () => {
if (!props.product) {
console.error('[PlanFormContainer] 无法提交: 产品数据为空')
return
}
// 调用模版组件的校验方法
if (templateRef.value && templateRef.value.validate) {
const isValid = templateRef.value.validate()
......
......@@ -135,6 +135,8 @@
<!-- 计划书表单容器 -->
<!-- 测试数据:后端接口和字段还没有准备好,使用 PlanFormContainer 进行的前端测试 -->
<!-- 使用 v-if 条件渲染,避免 selectedProduct 为 null 时的 prop 类型检查错误 -->
<view v-if="showPlanPopup && selectedProduct">
<PlanFormContainer
v-model:visible="showPlanPopup"
:product="selectedProduct"
......@@ -142,6 +144,7 @@
@submit="handlePlanSubmit"
/>
</view>
</view>
</template>
<script setup>
......