feat(plan): 添加客户姓名输入字段到所有保险计划模板
- 新增 PlanFieldName(NameInput)组件用于输入客户姓名 - 在重疾险、寿险、储蓄险三个模板中集成客户姓名字段 - 添加客户姓名必填校验逻辑 - 优化 NameInput 组件样式,使用 Tailwind CSS 和圆角边框 - 重构计划书页面代码,减少代码重复 影响文件: - src/components/plan/PlanFields/NameInput.vue (新增) - src/components/plan/PlanTemplates/*.vue (修改) - src/components/plan/PlanFormContainer.vue (重构) - src/pages/plan/index.vue (重构) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Showing
9 changed files
with
354 additions
and
427 deletions
| ... | @@ -18,6 +18,7 @@ declare module 'vue' { | ... | @@ -18,6 +18,7 @@ declare module 'vue' { |
| 18 | ListItemActions: typeof import('./src/components/list/ListItemActions/index.vue')['default'] | 18 | ListItemActions: typeof import('./src/components/list/ListItemActions/index.vue')['default'] |
| 19 | LoadMoreList: typeof import('./src/components/list/LoadMoreList/index.vue')['default'] | 19 | LoadMoreList: typeof import('./src/components/list/LoadMoreList/index.vue')['default'] |
| 20 | MaterialCard: typeof import('./src/components/cards/MaterialCard.vue')['default'] | 20 | MaterialCard: typeof import('./src/components/cards/MaterialCard.vue')['default'] |
| 21 | + NameInput: typeof import('./src/components/plan/PlanFields/NameInput.vue')['default'] | ||
| 21 | NavHeader: typeof import('./src/components/navigation/NavHeader.vue')['default'] | 22 | NavHeader: typeof import('./src/components/navigation/NavHeader.vue')['default'] |
| 22 | NutAvatar: typeof import('@nutui/nutui-taro')['Avatar'] | 23 | NutAvatar: typeof import('@nutui/nutui-taro')['Avatar'] |
| 23 | NutButton: typeof import('@nutui/nutui-taro')['Button'] | 24 | NutButton: typeof import('@nutui/nutui-taro')['Button'] | ... | ... |
| ... | @@ -5,6 +5,24 @@ | ... | @@ -5,6 +5,24 @@ |
| 5 | 5 | ||
| 6 | --- | 6 | --- |
| 7 | 7 | ||
| 8 | +## [2026-02-10] - 优化 NameInput 组件样式 | ||
| 9 | + | ||
| 10 | +### 优化 | ||
| 11 | +- **样式重构**:将 NameInput 组件从 Less 样式迁移到 Tailwind CSS | ||
| 12 | + - 遵循项目 Tailwind 优先的开发规范 | ||
| 13 | + - 移除冗余的 Less 代码 | ||
| 14 | +- **UI 改进**: | ||
| 15 | + - 添加可见的圆角边框(border-gray-200, rounded-[12rpx]) | ||
| 16 | + - 统一输入框的视觉风格 | ||
| 17 | + | ||
| 18 | +**详细信息**: | ||
| 19 | +- **影响文件**: src/components/plan/PlanFields/NameInput.vue | ||
| 20 | +- **技术栈**: Vue 3, Tailwind CSS | ||
| 21 | +- **测试状态**: 待验证 | ||
| 22 | +- **备注**: 响应用户需求,增强输入框视觉反馈 | ||
| 23 | + | ||
| 24 | +--- | ||
| 25 | + | ||
| 8 | ## [2026-02-10] - 重构 API 接口层代码 | 26 | ## [2026-02-10] - 重构 API 接口层代码 |
| 9 | 27 | ||
| 10 | ### 重构 | 28 | ### 重构 | ... | ... |
src/components/plan/PlanFields/NameInput.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2026-02-10 14:06:03 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2026-02-10 14:18:58 | ||
| 5 | + * @FilePath: /manulife-weapp/src/components/plan/PlanFields/NameInput.vue | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <!-- 姓名输入框 --> | ||
| 10 | + <div class="mb-5"> | ||
| 11 | + <label v-if="label" class="block mb-[16rpx] text-sm text-gray-600"> | ||
| 12 | + <text v-if="required" class="text-red-500">*</text> | ||
| 13 | + {{ label }} | ||
| 14 | + </label> | ||
| 15 | + <input | ||
| 16 | + :value="modelValue" | ||
| 17 | + :placeholder="placeholder" | ||
| 18 | + type="text" | ||
| 19 | + class="w-full h-[80rpx] px-[24rpx] text-[28rpx] text-[#333] rounded-[12rpx] border border-solid border-gray-200 box-border transition-all duration-300 focus:bg-white focus:border-[#4caf50] focus:outline-none placeholder:text-[#999]" | ||
| 20 | + :class="{ 'text-[#333]': modelValue }" | ||
| 21 | + @input="handleInput" | ||
| 22 | + /> | ||
| 23 | + </div> | ||
| 24 | +</template> | ||
| 25 | + | ||
| 26 | +<script setup> | ||
| 27 | +/** | ||
| 28 | + * 姓名输入框组件 | ||
| 29 | + * | ||
| 30 | + * @description 计划书表单的姓名输入字段 | ||
| 31 | + * - 支持双向绑定 | ||
| 32 | + * - 支持必填标识 | ||
| 33 | + * - 清晰的视觉反馈 | ||
| 34 | + * @author Claude Code | ||
| 35 | + * @example | ||
| 36 | + * <PlanFieldName | ||
| 37 | + * v-model="form.customer_name" | ||
| 38 | + * label="客户姓名" | ||
| 39 | + * placeholder="请输入客户姓名" | ||
| 40 | + * :required="true" | ||
| 41 | + * /> | ||
| 42 | + */ | ||
| 43 | +const props = defineProps({ | ||
| 44 | + /** | ||
| 45 | + * 字段值 | ||
| 46 | + * @type {string} | ||
| 47 | + */ | ||
| 48 | + modelValue: { | ||
| 49 | + type: String, | ||
| 50 | + default: '' | ||
| 51 | + }, | ||
| 52 | + /** | ||
| 53 | + * 标签文本 | ||
| 54 | + * @type {string} | ||
| 55 | + */ | ||
| 56 | + label: { | ||
| 57 | + type: String, | ||
| 58 | + default: '' | ||
| 59 | + }, | ||
| 60 | + /** | ||
| 61 | + * 占位符文本 | ||
| 62 | + * @type {string} | ||
| 63 | + */ | ||
| 64 | + placeholder: { | ||
| 65 | + type: String, | ||
| 66 | + default: '请输入姓名' | ||
| 67 | + }, | ||
| 68 | + /** | ||
| 69 | + * 是否必填 | ||
| 70 | + * @type {boolean} | ||
| 71 | + */ | ||
| 72 | + required: { | ||
| 73 | + type: Boolean, | ||
| 74 | + default: false | ||
| 75 | + } | ||
| 76 | +}) | ||
| 77 | + | ||
| 78 | +/** | ||
| 79 | + * 组件事件 | ||
| 80 | + */ | ||
| 81 | +const emit = defineEmits([ | ||
| 82 | + /** | ||
| 83 | + * 更新值事件 | ||
| 84 | + * @event update:modelValue | ||
| 85 | + * @param {string} value - 输入的值 | ||
| 86 | + */ | ||
| 87 | + 'update:modelValue' | ||
| 88 | +]) | ||
| 89 | + | ||
| 90 | +/** | ||
| 91 | + * 处理输入事件 | ||
| 92 | + * @param {Event} e - 输入事件对象 | ||
| 93 | + */ | ||
| 94 | +const handleInput = (e) => { | ||
| 95 | + const value = e.detail.value | ||
| 96 | + emit('update:modelValue', value) | ||
| 97 | +} | ||
| 98 | +</script> |
| ... | @@ -42,11 +42,13 @@ | ... | @@ -42,11 +42,13 @@ |
| 42 | * /> | 42 | * /> |
| 43 | */ | 43 | */ |
| 44 | import { ref, computed, watch, nextTick } from 'vue' | 44 | import { ref, computed, watch, nextTick } from 'vue' |
| 45 | +import Taro from '@tarojs/taro' | ||
| 45 | import PlanPopupNew from './PlanPopupNew.vue' | 46 | import PlanPopupNew from './PlanPopupNew.vue' |
| 46 | import LifeInsuranceTemplate from './PlanTemplates/LifeInsuranceTemplate.vue' | 47 | import LifeInsuranceTemplate from './PlanTemplates/LifeInsuranceTemplate.vue' |
| 47 | import CriticalIllnessTemplate from './PlanTemplates/CriticalIllnessTemplate.vue' | 48 | import CriticalIllnessTemplate from './PlanTemplates/CriticalIllnessTemplate.vue' |
| 48 | import SavingsTemplate from './PlanTemplates/SavingsTemplate.vue' | 49 | import SavingsTemplate from './PlanTemplates/SavingsTemplate.vue' |
| 49 | import { PLAN_TEMPLATES } from '@/config/plan-templates' | 50 | import { PLAN_TEMPLATES } from '@/config/plan-templates' |
| 51 | +import { addAPI } from '@/api/plan' | ||
| 50 | 52 | ||
| 51 | /** | 53 | /** |
| 52 | * 组件属性 | 54 | * 组件属性 |
| ... | @@ -251,44 +253,128 @@ const formatAmounts = (data) => { | ... | @@ -251,44 +253,128 @@ const formatAmounts = (data) => { |
| 251 | 253 | ||
| 252 | /** | 254 | /** |
| 253 | * 提交表单 | 255 | * 提交表单 |
| 254 | - * @description 将表单数据和产品信息一起提交 | 256 | + * @description 将表单数据和产品信息提交到后端 API |
| 255 | * | 257 | * |
| 256 | - * ⚠️ 注意:接口未完成,当前仅为前端测试 | 258 | + * @returns {Promise<boolean>} 是否提交成功 |
| 257 | - * TODO: 后端接口准备就绪后,需要调用 submitPlanAPI 提交表单数据 | ||
| 258 | */ | 259 | */ |
| 259 | const submit = async () => { | 260 | const submit = async () => { |
| 260 | if (!props.product) { | 261 | if (!props.product) { |
| 261 | console.error('[PlanFormContainer] 无法提交: 产品数据为空') | 262 | console.error('[PlanFormContainer] 无法提交: 产品数据为空') |
| 262 | - return | 263 | + Taro.showToast({ |
| 264 | + title: '产品数据为空', | ||
| 265 | + icon: 'none', | ||
| 266 | + duration: 2000 | ||
| 267 | + }) | ||
| 268 | + return false | ||
| 263 | } | 269 | } |
| 264 | 270 | ||
| 265 | // 调用模版组件的校验方法 | 271 | // 调用模版组件的校验方法 |
| 266 | if (templateRef.value && templateRef.value.validate) { | 272 | if (templateRef.value && templateRef.value.validate) { |
| 267 | const isValid = templateRef.value.validate() | 273 | const isValid = templateRef.value.validate() |
| 268 | if (!isValid) { | 274 | if (!isValid) { |
| 269 | - return | 275 | + return false |
| 270 | } | 276 | } |
| 271 | } | 277 | } |
| 272 | 278 | ||
| 273 | - // 格式化金额数据(便于调试查看) | 279 | + // 显示加载提示 |
| 274 | - const formattedData = formatAmounts(formData.value) | 280 | + Taro.showLoading({ |
| 281 | + title: '提交中...', | ||
| 282 | + mask: true | ||
| 283 | + }) | ||
| 275 | 284 | ||
| 276 | - console.log('[PlanFormContainer] 提交计划书:', { | 285 | + try { |
| 277 | - product_id: props.product.id, | 286 | + // 字段名映射:将表单字段名映射为 API 期望的字段名 |
| 278 | - product_name: props.product.product_name, | 287 | + const fieldMapping = { |
| 279 | - form_sn: props.product.form_sn, | 288 | + customer_name: 'customer_name', // 客户姓名(已直接使用) |
| 280 | - form_data: formattedData // ← 打印格式化后的数据(元) | 289 | + gender: 'customer_gender', // 性别 → customer_gender |
| 290 | + age: 'customer_age', // 年龄 → customer_age | ||
| 291 | + birthday: 'customer_birthday', // 出生年月日 → customer_birthday | ||
| 292 | + smoker: 'smoker', // 是否吸烟(保持不变) | ||
| 293 | + coverage: 'annual_premium', // 保额/年缴保费 → annual_premium | ||
| 294 | + payment_period: 'payment_years', // 缴费年期 → payment_years | ||
| 295 | + withdrawal_enabled: 'allow_reduce_amount', // 是否容许减少名义金额 | ||
| 296 | + withdrawal_mode: 'withdrawal_option', // 提取选项 | ||
| 297 | + specified_amount_type: null, // 提取方式(后端可能不需要) | ||
| 298 | + withdrawal_start_age: 'withdrawal_start_age', // 提取开始年龄 | ||
| 299 | + withdrawal_period: 'withdrawal_period', // 提取期 | ||
| 300 | + currency_type: 'currency_type' // 币种类型 | ||
| 301 | + } | ||
| 302 | + | ||
| 303 | + // 构建请求数据 | ||
| 304 | + const requestData = { | ||
| 305 | + product_id: props.product.id | ||
| 306 | + } | ||
| 307 | + | ||
| 308 | + // 映射表单字段到 API 字段 | ||
| 309 | + Object.keys(formData.value).forEach(key => { | ||
| 310 | + const apiField = fieldMapping[key] | ||
| 311 | + | ||
| 312 | + if (apiField) { | ||
| 313 | + // 有映射:使用映射后的字段名 | ||
| 314 | + requestData[apiField] = formData.value[key] | ||
| 315 | + } else if (key === 'total_amount') { | ||
| 316 | + // 特殊处理:总保费(分 → 元) | ||
| 317 | + requestData.total_premium = (formData.value[key] / 100).toFixed(2) | ||
| 318 | + } else { | ||
| 319 | + // 无映射:保持原字段名 | ||
| 320 | + requestData[key] = formData.value[key] | ||
| 321 | + } | ||
| 281 | }) | 322 | }) |
| 282 | 323 | ||
| 283 | - console.log('[PlanFormContainer] 原始数据(分):', formData.value) | 324 | + // 添加币种类型(如果有配置) |
| 325 | + if (templateConfig.value?.config?.currency) { | ||
| 326 | + requestData.currency_type = templateConfig.value.config.currency | ||
| 327 | + } | ||
| 328 | + | ||
| 329 | + console.log('[PlanFormContainer] 提交计划书请求数据:', requestData) | ||
| 330 | + console.log('[PlanFormContainer] 字段映射:', fieldMapping) | ||
| 331 | + | ||
| 332 | + // 调用 API | ||
| 333 | + const res = await addAPI(requestData) | ||
| 334 | + | ||
| 335 | + if (res.code === 1) { | ||
| 336 | + Taro.hideLoading() | ||
| 284 | 337 | ||
| 285 | - // 发送提交事件(携带原始表单数据给父组件,单位:分) | 338 | + Taro.showToast({ |
| 339 | + title: '提交成功', | ||
| 340 | + icon: 'success', | ||
| 341 | + duration: 2000 | ||
| 342 | + }) | ||
| 343 | + | ||
| 344 | + // 发送提交成功事件(携带 order_id) | ||
| 286 | emit('submit', { | 345 | emit('submit', { |
| 346 | + success: true, | ||
| 347 | + order_id: res.data?.order_id, | ||
| 287 | product_id: props.product.id, | 348 | product_id: props.product.id, |
| 288 | - form_sn: props.product.form_sn, | 349 | + form_sn: props.product.form_sn |
| 289 | - form_data: formData.value // ← 发送原始数据(分),接口需要 | 350 | + }) |
| 351 | + | ||
| 352 | + return true | ||
| 353 | + } else { | ||
| 354 | + Taro.hideLoading() | ||
| 355 | + | ||
| 356 | + Taro.showToast({ | ||
| 357 | + title: res.msg || '提交失败', | ||
| 358 | + icon: 'none', | ||
| 359 | + duration: 2000 | ||
| 360 | + }) | ||
| 361 | + | ||
| 362 | + return false | ||
| 363 | + } | ||
| 364 | + } catch (error) { | ||
| 365 | + Taro.hideLoading() | ||
| 366 | + | ||
| 367 | + console.error('[PlanFormContainer] 提交计划书失败:', error) | ||
| 368 | + | ||
| 369 | + Taro.showToast({ | ||
| 370 | + title: '网络异常,请重试', | ||
| 371 | + icon: 'none', | ||
| 372 | + duration: 2000 | ||
| 290 | }) | 373 | }) |
| 291 | 374 | ||
| 375 | + return false | ||
| 376 | + } | ||
| 377 | + | ||
| 292 | // ✅ 不在这里重置表单,让父组件先处理数据 | 378 | // ✅ 不在这里重置表单,让父组件先处理数据 |
| 293 | // 重置逻辑交给 close() 函数处理(关闭弹窗时自动清空) | 379 | // 重置逻辑交给 close() 函数处理(关闭弹窗时自动清空) |
| 294 | } | 380 | } | ... | ... |
| 1 | <template> | 1 | <template> |
| 2 | <div v-if="config"> | 2 | <div v-if="config"> |
| 3 | + <!-- 客户姓名 --> | ||
| 4 | + <PlanFieldName | ||
| 5 | + v-model="form.customer_name" | ||
| 6 | + label="客户姓名" | ||
| 7 | + placeholder="请输入客户姓名" | ||
| 8 | + :required="true" | ||
| 9 | + class="mb-5" | ||
| 10 | + /> | ||
| 11 | + | ||
| 3 | <!-- 性别 --> | 12 | <!-- 性别 --> |
| 4 | <PlanFieldRadio | 13 | <PlanFieldRadio |
| 5 | v-model="form.gender" | 14 | v-model="form.gender" |
| ... | @@ -82,6 +91,7 @@ | ... | @@ -82,6 +91,7 @@ |
| 82 | */ | 91 | */ |
| 83 | import { reactive, watch } from 'vue' | 92 | import { reactive, watch } from 'vue' |
| 84 | import Taro from '@tarojs/taro' | 93 | import Taro from '@tarojs/taro' |
| 94 | +import PlanFieldName from '../PlanFields/NameInput.vue' | ||
| 85 | import PlanFieldAgePicker from '../PlanFields/AgePickerGlobal.vue' | 95 | import PlanFieldAgePicker from '../PlanFields/AgePickerGlobal.vue' |
| 86 | import PlanFieldAmount from '../PlanFields/AmountKeyboard.vue' | 96 | import PlanFieldAmount from '../PlanFields/AmountKeyboard.vue' |
| 87 | import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue' | 97 | import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue' |
| ... | @@ -228,6 +238,10 @@ const onBirthdayChange = (birthday) => { | ... | @@ -228,6 +238,10 @@ const onBirthdayChange = (birthday) => { |
| 228 | * @returns {boolean} 是否通过校验 | 238 | * @returns {boolean} 是否通过校验 |
| 229 | */ | 239 | */ |
| 230 | const validate = () => { | 240 | const validate = () => { |
| 241 | + if (!form.customer_name || !form.customer_name.trim()) { | ||
| 242 | + Taro.showToast({ title: '请输入客户姓名', icon: 'none' }) | ||
| 243 | + return false | ||
| 244 | + } | ||
| 231 | if (!form.gender) { | 245 | if (!form.gender) { |
| 232 | Taro.showToast({ title: '请选择性别', icon: 'none' }) | 246 | Taro.showToast({ title: '请选择性别', icon: 'none' }) |
| 233 | return false | 247 | return false | ... | ... |
| 1 | <template> | 1 | <template> |
| 2 | <div v-if="config"> | 2 | <div v-if="config"> |
| 3 | + <!-- 客户姓名 --> | ||
| 4 | + <PlanFieldName | ||
| 5 | + v-model="form.customer_name" | ||
| 6 | + label="客户姓名" | ||
| 7 | + placeholder="请输入客户姓名" | ||
| 8 | + :required="true" | ||
| 9 | + class="mb-5" | ||
| 10 | + /> | ||
| 11 | + | ||
| 3 | <!-- 性别 --> | 12 | <!-- 性别 --> |
| 4 | <PlanFieldRadio | 13 | <PlanFieldRadio |
| 5 | v-model="form.gender" | 14 | v-model="form.gender" |
| ... | @@ -82,6 +91,7 @@ | ... | @@ -82,6 +91,7 @@ |
| 82 | */ | 91 | */ |
| 83 | import { reactive, watch, toRefs } from 'vue' | 92 | import { reactive, watch, toRefs } from 'vue' |
| 84 | import Taro from '@tarojs/taro' | 93 | import Taro from '@tarojs/taro' |
| 94 | +import PlanFieldName from '../PlanFields/NameInput.vue' | ||
| 85 | import PlanFieldAgePicker from '../PlanFields/AgePickerGlobal.vue' | 95 | import PlanFieldAgePicker from '../PlanFields/AgePickerGlobal.vue' |
| 86 | import PlanFieldAmount from '../PlanFields/AmountKeyboard.vue' | 96 | import PlanFieldAmount from '../PlanFields/AmountKeyboard.vue' |
| 87 | import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue' | 97 | import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue' |
| ... | @@ -231,6 +241,10 @@ const onBirthdayChange = (birthday) => { | ... | @@ -231,6 +241,10 @@ const onBirthdayChange = (birthday) => { |
| 231 | * @returns {boolean} 是否通过校验 | 241 | * @returns {boolean} 是否通过校验 |
| 232 | */ | 242 | */ |
| 233 | const validate = () => { | 243 | const validate = () => { |
| 244 | + if (!form.customer_name || !form.customer_name.trim()) { | ||
| 245 | + Taro.showToast({ title: '请输入客户姓名', icon: 'none' }) | ||
| 246 | + return false | ||
| 247 | + } | ||
| 234 | if (!form.gender) { | 248 | if (!form.gender) { |
| 235 | Taro.showToast({ title: '请选择性别', icon: 'none' }) | 249 | Taro.showToast({ title: '请选择性别', icon: 'none' }) |
| 236 | return false | 250 | return false | ... | ... |
| 1 | <template> | 1 | <template> |
| 2 | <div v-if="config"> | 2 | <div v-if="config"> |
| 3 | + <!-- 客户姓名 --> | ||
| 4 | + <PlanFieldName | ||
| 5 | + v-model="form.customer_name" | ||
| 6 | + label="客户姓名" | ||
| 7 | + placeholder="请输入客户姓名" | ||
| 8 | + :required="true" | ||
| 9 | + class="mb-5" | ||
| 10 | + /> | ||
| 11 | + | ||
| 3 | <!-- 性别 --> | 12 | <!-- 性别 --> |
| 4 | <PlanFieldRadio | 13 | <PlanFieldRadio |
| 5 | v-model="form.gender" | 14 | v-model="form.gender" |
| ... | @@ -207,6 +216,7 @@ | ... | @@ -207,6 +216,7 @@ |
| 207 | */ | 216 | */ |
| 208 | import { reactive, watch, computed } from 'vue' | 217 | import { reactive, watch, computed } from 'vue' |
| 209 | import Taro from '@tarojs/taro' | 218 | import Taro from '@tarojs/taro' |
| 219 | +import PlanFieldName from '../PlanFields/NameInput.vue' | ||
| 210 | import PlanFieldAgePicker from '../PlanFields/AgePickerGlobal.vue' | 220 | import PlanFieldAgePicker from '../PlanFields/AgePickerGlobal.vue' |
| 211 | import PlanFieldAmount from '../PlanFields/AmountKeyboard.vue' | 221 | import PlanFieldAmount from '../PlanFields/AmountKeyboard.vue' |
| 212 | import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue' | 222 | import PlanFieldDatePicker from '../PlanFields/DatePickerGlobal.vue' |
| ... | @@ -457,6 +467,10 @@ watch( | ... | @@ -457,6 +467,10 @@ watch( |
| 457 | */ | 467 | */ |
| 458 | const validate = () => { | 468 | const validate = () => { |
| 459 | // 基础字段校验 | 469 | // 基础字段校验 |
| 470 | + if (!form.customer_name || !form.customer_name.trim()) { | ||
| 471 | + Taro.showToast({ title: '请输入客户姓名', icon: 'none' }) | ||
| 472 | + return false | ||
| 473 | + } | ||
| 460 | if (!form.gender) { | 474 | if (!form.gender) { |
| 461 | Taro.showToast({ title: '请选择性别', icon: 'none' }) | 475 | Taro.showToast({ title: '请选择性别', icon: 'none' }) |
| 462 | return false | 476 | return false | ... | ... |
| ... | @@ -121,7 +121,6 @@ import MaterialCard from '@/components/cards/MaterialCard.vue'; | ... | @@ -121,7 +121,6 @@ import MaterialCard from '@/components/cards/MaterialCard.vue'; |
| 121 | import { listAPI } from '@/api/get_product'; | 121 | import { listAPI } from '@/api/get_product'; |
| 122 | import { weekHotAPI } from '@/api/file'; | 122 | import { weekHotAPI } from '@/api/file'; |
| 123 | import { homeIconAPI } from '@/api/home'; | 123 | import { homeIconAPI } from '@/api/home'; |
| 124 | -import { mockHotProductsListAPI } from '@/api/mock/hotProducts'; | ||
| 125 | 124 | ||
| 126 | 125 | ||
| 127 | // User Store | 126 | // User Store |
| ... | @@ -246,41 +245,29 @@ const fetchHomeIcons = async () => { | ... | @@ -246,41 +245,29 @@ const fetchHomeIcons = async () => { |
| 246 | const hotProducts = ref([]); | 245 | const hotProducts = ref([]); |
| 247 | 246 | ||
| 248 | /** | 247 | /** |
| 249 | - * Mock 数据开关 | ||
| 250 | - * @description 开发环境默认使用 mock 数据测试计划书功能 | ||
| 251 | - * 生产环境必须设置为 false | ||
| 252 | - */ | ||
| 253 | -const USE_MOCK_DATA = process.env.NODE_ENV === 'development'; // 开发环境默认使用 mock 数据 | ||
| 254 | - | ||
| 255 | -/** | ||
| 256 | * 获取热卖产品列表 | 248 | * 获取热卖产品列表 |
| 257 | * | 249 | * |
| 258 | - * @description 根据 USE_MOCK_DATA 开关决定使用 mock 数据还是调用 API | 250 | + * @description 调用 listAPI 获取真实的热卖产品数据 |
| 259 | - * - 开发环境使用 mock 数据测试计划书功能 | ||
| 260 | - * - 生产环境调用 listAPI 获取真实数据 | ||
| 261 | */ | 251 | */ |
| 262 | const fetchHotProducts = async () => { | 252 | const fetchHotProducts = async () => { |
| 263 | try { | 253 | try { |
| 264 | - // 开发测试环境:使用 mock 数据 | 254 | + console.log('[Index] 获取热卖产品'); |
| 265 | - if (USE_MOCK_DATA) { | ||
| 266 | - console.log('[Index] 使用 mock 数据获取热卖产品'); | ||
| 267 | - const res = await mockHotProductsListAPI({ recommend: 'hot' }); | ||
| 268 | - if (res.code === 1 && res.data && res.data.list) { | ||
| 269 | - hotProducts.value = res.data.list; | ||
| 270 | - console.log('[Index] Mock 数据加载成功,产品数量:', res.data.list.length); | ||
| 271 | - } | ||
| 272 | - return; | ||
| 273 | - } | ||
| 274 | 255 | ||
| 275 | - // 生产环境:调用真实 API | 256 | + // 调用真实 API |
| 276 | const res = await listAPI({ | 257 | const res = await listAPI({ |
| 277 | recommend: 'hot' | 258 | recommend: 'hot' |
| 278 | }); | 259 | }); |
| 260 | + | ||
| 279 | if (res.code === 1 && res.data && res.data.list) { | 261 | if (res.code === 1 && res.data && res.data.list) { |
| 280 | hotProducts.value = res.data.list; | 262 | hotProducts.value = res.data.list; |
| 263 | + console.log('[Index] 热卖产品加载成功,数量:', res.data.list.length); | ||
| 264 | + } else { | ||
| 265 | + console.warn('[Index] 热卖产品返回数据格式不正确:', res); | ||
| 266 | + hotProducts.value = []; | ||
| 281 | } | 267 | } |
| 282 | } catch (err) { | 268 | } catch (err) { |
| 283 | - console.error('获取热卖产品失败:', err); | 269 | + console.error('[Index] 获取热卖产品失败:', err); |
| 270 | + hotProducts.value = []; | ||
| 284 | } | 271 | } |
| 285 | }; | 272 | }; |
| 286 | 273 | ... | ... |
| ... | @@ -122,9 +122,10 @@ | ... | @@ -122,9 +122,10 @@ |
| 122 | </template> | 122 | </template> |
| 123 | 123 | ||
| 124 | <script setup> | 124 | <script setup> |
| 125 | -import { ref, computed, nextTick } from 'vue' | 125 | +import { ref, nextTick } from 'vue' |
| 126 | import Taro, { useLoad, useReachBottom } from '@tarojs/taro' | 126 | import Taro, { useLoad, useReachBottom } from '@tarojs/taro' |
| 127 | import { useFileOperation } from '@/composables/useFileOperation' | 127 | import { useFileOperation } from '@/composables/useFileOperation' |
| 128 | +import { listAPI } from '@/api/plan' | ||
| 128 | import NavHeader from '@/components/navigation/NavHeader.vue' | 129 | import NavHeader from '@/components/navigation/NavHeader.vue' |
| 129 | import ListItemActions from '@/components/list/ListItemActions/index.vue' | 130 | import ListItemActions from '@/components/list/ListItemActions/index.vue' |
| 130 | import SearchBar from '@/components/forms/SearchBar.vue' | 131 | import SearchBar from '@/components/forms/SearchBar.vue' |
| ... | @@ -140,7 +141,7 @@ const loadingMore = ref(false) | ... | @@ -140,7 +141,7 @@ const loadingMore = ref(false) |
| 140 | const hasMore = ref(true) | 141 | const hasMore = ref(true) |
| 141 | const currentList = ref([]) | 142 | const currentList = ref([]) |
| 142 | const currentPage = ref(0) // 当前页码(从0开始) | 143 | const currentPage = ref(0) // 当前页码(从0开始) |
| 143 | -const pageSize = 20 // 每页数量(临时增加到 20 条以便测试滚动) | 144 | +const pageSize = 20 // 每页数量 |
| 144 | 145 | ||
| 145 | /** | 146 | /** |
| 146 | * Tab 数据源 | 147 | * Tab 数据源 |
| ... | @@ -153,347 +154,36 @@ const tabsData = ref([ | ... | @@ -153,347 +154,36 @@ const tabsData = ref([ |
| 153 | ]) | 154 | ]) |
| 154 | 155 | ||
| 155 | /** | 156 | /** |
| 156 | - * Mock 数据:计划书列表(扩展到30条用于测试分页) | 157 | + * 从 API 数据转换为组件数据格式 |
| 157 | - * | 158 | + * @description 将 API 返回的数据结构转换为组件使用的格式 |
| 158 | - * @description 使用真实 PDF 文件进行测试 | 159 | + * @param {Object} apiItem - API 返回的计划书对象 |
| 159 | - * downloadUrl 使用 Mozilla 的公开 PDF 测试文件 | 160 | + * @returns {Object} 组件使用的计划书对象 |
| 160 | */ | 161 | */ |
| 161 | -const allList = ref([ | 162 | +const transformApiItem = (apiItem) => { |
| 162 | - // 第1页 (0-9) | 163 | + // 获取第一个文件(如果有) |
| 163 | - { | 164 | + const firstFile = apiItem.proposal_files && apiItem.proposal_files[0] |
| 164 | - id: 1, | 165 | + |
| 165 | - title: '家庭财富传承保障计划(分红)', | 166 | + return { |
| 166 | - client: '客户:李*生', | 167 | + id: apiItem.id, |
| 167 | - date: '2024-03-15 10:12', | 168 | + title: apiItem.product_name || '未命名计划书', |
| 168 | - tag: '年金保险', | 169 | + client: `客户:${apiItem.customer_name || '未知'}`, |
| 169 | - status: 'generated', | 170 | + date: apiItem.created_time || '', |
| 170 | - fileName: '家庭财富传承保障计划(分红).pdf', | 171 | + tag: apiItem.categories && apiItem.categories.length > 0 ? apiItem.categories[0] : '', |
| 171 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | 172 | + status: apiItem.order_status === 'processing' ? 'processing' : 'generated', |
| 172 | - }, | 173 | + fileName: firstFile?.file_name || '', |
| 173 | - { | 174 | + downloadUrl: firstFile?.file_url || '', |
| 174 | - id: 2, | 175 | + // 保存完整的原始数据 |
| 175 | - title: '财富传承保障金融理财计划(分红)', | 176 | + _raw: apiItem |
| 176 | - client: '客户:孙*', | ||
| 177 | - date: '2024-03-14 12:01', | ||
| 178 | - tag: '', | ||
| 179 | - status: 'processing', | ||
| 180 | - fileName: '', | ||
| 181 | - downloadUrl: '' | ||
| 182 | - }, | ||
| 183 | - { | ||
| 184 | - id: 3, | ||
| 185 | - title: '企业高管年金计划', | ||
| 186 | - client: '客户:王*江', | ||
| 187 | - date: '2024-03-13 09:23', | ||
| 188 | - tag: '年金保险', | ||
| 189 | - status: 'generated', | ||
| 190 | - fileName: '企业高管年金计划.pdf', | ||
| 191 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 192 | - }, | ||
| 193 | - { | ||
| 194 | - id: 4, | ||
| 195 | - title: '家庭财富传承保障计划', | ||
| 196 | - client: '客户:李*生', | ||
| 197 | - date: '2024-03-12 15:12', | ||
| 198 | - tag: '年金保险', | ||
| 199 | - status: 'generated', | ||
| 200 | - fileName: '家庭财富传承保障计划.pdf', | ||
| 201 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 202 | - }, | ||
| 203 | - { | ||
| 204 | - id: 5, | ||
| 205 | - title: '高净值家庭资产配置计划', | ||
| 206 | - client: '客户:赵*琪', | ||
| 207 | - date: '2024-03-11 18:40', | ||
| 208 | - tag: '资产配置', | ||
| 209 | - status: 'generated', | ||
| 210 | - fileName: '高净值家庭资产配置计划.pdf', | ||
| 211 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 212 | - }, | ||
| 213 | - { | ||
| 214 | - id: 6, | ||
| 215 | - title: '企业主税务筹划方案', | ||
| 216 | - client: '客户:陈*明', | ||
| 217 | - date: '2024-03-11 11:05', | ||
| 218 | - tag: '税务筹划', | ||
| 219 | - status: 'processing', | ||
| 220 | - fileName: '', | ||
| 221 | - downloadUrl: '' | ||
| 222 | - }, | ||
| 223 | - { | ||
| 224 | - id: 7, | ||
| 225 | - title: '家庭保障升级计划(重疾)', | ||
| 226 | - client: '客户:周*然', | ||
| 227 | - date: '2024-03-10 14:22', | ||
| 228 | - tag: '健康保障', | ||
| 229 | - status: 'generated', | ||
| 230 | - fileName: '家庭保障升级计划(重疾).pdf', | ||
| 231 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 232 | - }, | ||
| 233 | - { | ||
| 234 | - id: 8, | ||
| 235 | - title: '家庭教育金规划方案', | ||
| 236 | - client: '客户:许*雅', | ||
| 237 | - date: '2024-03-10 09:48', | ||
| 238 | - tag: '教育金', | ||
| 239 | - status: 'generated', | ||
| 240 | - fileName: '家庭教育金规划方案.pdf', | ||
| 241 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 242 | - }, | ||
| 243 | - { | ||
| 244 | - id: 9, | ||
| 245 | - title: '中长期养老规划', | ||
| 246 | - client: '客户:刘*文', | ||
| 247 | - date: '2024-03-09 16:05', | ||
| 248 | - tag: '养老规划', | ||
| 249 | - status: 'processing', | ||
| 250 | - fileName: '', | ||
| 251 | - downloadUrl: '' | ||
| 252 | - }, | ||
| 253 | - { | ||
| 254 | - id: 10, | ||
| 255 | - title: '企业员工福利保障计划', | ||
| 256 | - client: '客户:沈*杰', | ||
| 257 | - date: '2024-03-09 10:33', | ||
| 258 | - tag: '团体保障', | ||
| 259 | - status: 'generated', | ||
| 260 | - fileName: '企业员工福利保障计划.pdf', | ||
| 261 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 262 | - }, | ||
| 263 | - // 第2页 (10-19) | ||
| 264 | - { | ||
| 265 | - id: 11, | ||
| 266 | - title: '家族信托规划方案', | ||
| 267 | - client: '客户:唐*豪', | ||
| 268 | - date: '2024-03-08 17:50', | ||
| 269 | - tag: '家族信托', | ||
| 270 | - status: 'generated', | ||
| 271 | - fileName: '家族信托规划方案.pdf', | ||
| 272 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 273 | - }, | ||
| 274 | - { | ||
| 275 | - id: 12, | ||
| 276 | - title: '企业接班人保障方案', | ||
| 277 | - client: '客户:何*峰', | ||
| 278 | - date: '2024-03-08 09:12', | ||
| 279 | - tag: '传承保障', | ||
| 280 | - status: 'generated', | ||
| 281 | - fileName: '企业接班人保障方案.pdf', | ||
| 282 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 283 | - }, | ||
| 284 | - { | ||
| 285 | - id: 13, | ||
| 286 | - title: '高端医疗健康保障计划', | ||
| 287 | - client: '客户:张*敏', | ||
| 288 | - date: '2024-03-07 14:30', | ||
| 289 | - tag: '健康保障', | ||
| 290 | - status: 'generated', | ||
| 291 | - fileName: '高端医疗健康保障计划.pdf', | ||
| 292 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 293 | - }, | ||
| 294 | - { | ||
| 295 | - id: 14, | ||
| 296 | - title: '子女教育金储备计划', | ||
| 297 | - client: '客户:马*丽', | ||
| 298 | - date: '2024-03-07 10:15', | ||
| 299 | - tag: '教育金', | ||
| 300 | - status: 'processing', | ||
| 301 | - fileName: '', | ||
| 302 | - downloadUrl: '' | ||
| 303 | - }, | ||
| 304 | - { | ||
| 305 | - id: 15, | ||
| 306 | - title: '家庭财富增值规划', | ||
| 307 | - client: '客户:林*涛', | ||
| 308 | - date: '2024-03-06 16:45', | ||
| 309 | - tag: '资产配置', | ||
| 310 | - status: 'generated', | ||
| 311 | - fileName: '家庭财富增值规划.pdf', | ||
| 312 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 313 | - }, | ||
| 314 | - { | ||
| 315 | - id: 16, | ||
| 316 | - title: '企业年金优化方案', | ||
| 317 | - client: '客户:吴*华', | ||
| 318 | - date: '2024-03-06 11:20', | ||
| 319 | - tag: '年金保险', | ||
| 320 | - status: 'generated', | ||
| 321 | - fileName: '企业年金优化方案.pdf', | ||
| 322 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 323 | - }, | ||
| 324 | - { | ||
| 325 | - id: 17, | ||
| 326 | - title: '个人税收筹划方案', | ||
| 327 | - client: '客户:郑*强', | ||
| 328 | - date: '2024-03-05 15:10', | ||
| 329 | - tag: '税务筹划', | ||
| 330 | - status: 'processing', | ||
| 331 | - fileName: '', | ||
| 332 | - downloadUrl: '' | ||
| 333 | - }, | ||
| 334 | - { | ||
| 335 | - id: 18, | ||
| 336 | - title: '全面家庭保障计划', | ||
| 337 | - client: '客户:黄*明', | ||
| 338 | - date: '2024-03-05 09:50', | ||
| 339 | - tag: '健康保障', | ||
| 340 | - status: 'generated', | ||
| 341 | - fileName: '全面家庭保障计划.pdf', | ||
| 342 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 343 | - }, | ||
| 344 | - { | ||
| 345 | - id: 19, | ||
| 346 | - title: '子女教育金累积计划', | ||
| 347 | - client: '客户:谢*芳', | ||
| 348 | - date: '2024-03-04 14:25', | ||
| 349 | - tag: '教育金', | ||
| 350 | - status: 'generated', | ||
| 351 | - fileName: '子女教育金累积计划.pdf', | ||
| 352 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 353 | - }, | ||
| 354 | - { | ||
| 355 | - id: 20, | ||
| 356 | - title: '退休养老规划方案', | ||
| 357 | - client: '客户:冯*伟', | ||
| 358 | - date: '2024-03-04 10:40', | ||
| 359 | - tag: '养老规划', | ||
| 360 | - status: 'generated', | ||
| 361 | - fileName: '退休养老规划方案.pdf', | ||
| 362 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 363 | - }, | ||
| 364 | - // 第3页 (20-29) | ||
| 365 | - { | ||
| 366 | - id: 21, | ||
| 367 | - title: '企业财产保险规划', | ||
| 368 | - client: '客户:袁*杰', | ||
| 369 | - date: '2024-03-03 16:20', | ||
| 370 | - tag: '团体保障', | ||
| 371 | - status: 'generated', | ||
| 372 | - fileName: '企业财产保险规划.pdf', | ||
| 373 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 374 | - }, | ||
| 375 | - { | ||
| 376 | - id: 22, | ||
| 377 | - title: '家庭财产保全计划', | ||
| 378 | - client: '客户:彭*婷', | ||
| 379 | - date: '2024-03-03 11:05', | ||
| 380 | - tag: '传承保障', | ||
| 381 | - status: 'processing', | ||
| 382 | - fileName: '', | ||
| 383 | - downloadUrl: '' | ||
| 384 | - }, | ||
| 385 | - { | ||
| 386 | - id: 23, | ||
| 387 | - title: '高端医疗保险方案', | ||
| 388 | - client: '客户:曹*强', | ||
| 389 | - date: '2024-03-02 15:35', | ||
| 390 | - tag: '健康保障', | ||
| 391 | - status: 'generated', | ||
| 392 | - fileName: '高端医疗保险方案.pdf', | ||
| 393 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 394 | - }, | ||
| 395 | - { | ||
| 396 | - id: 24, | ||
| 397 | - title: '企业资产传承规划', | ||
| 398 | - client: '客户:许*华', | ||
| 399 | - date: '2024-03-02 10:15', | ||
| 400 | - tag: '家族信托', | ||
| 401 | - status: 'generated', | ||
| 402 | - fileName: '企业资产传承规划.pdf', | ||
| 403 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 404 | - }, | ||
| 405 | - { | ||
| 406 | - id: 25, | ||
| 407 | - title: '子女海外教育金计划', | ||
| 408 | - client: '客户:邓*丽', | ||
| 409 | - date: '2024-03-01 14:50', | ||
| 410 | - tag: '教育金', | ||
| 411 | - status: 'generated', | ||
| 412 | - fileName: '子女海外教育金计划.pdf', | ||
| 413 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 414 | - }, | ||
| 415 | - { | ||
| 416 | - id: 26, | ||
| 417 | - title: '个人财富保全方案', | ||
| 418 | - client: '客户:萧*敏', | ||
| 419 | - date: '2024-03-01 09:30', | ||
| 420 | - tag: '资产配置', | ||
| 421 | - status: 'processing', | ||
| 422 | - fileName: '', | ||
| 423 | - downloadUrl: '' | ||
| 424 | - }, | ||
| 425 | - { | ||
| 426 | - id: 27, | ||
| 427 | - title: '企业员工激励计划', | ||
| 428 | - client: '客户:田*勇', | ||
| 429 | - date: '2024-02-29 16:10', | ||
| 430 | - tag: '团体保障', | ||
| 431 | - status: 'generated', | ||
| 432 | - fileName: '企业员工激励计划.pdf', | ||
| 433 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 434 | - }, | ||
| 435 | - { | ||
| 436 | - id: 28, | ||
| 437 | - title: '家庭健康保障计划', | ||
| 438 | - client: '客户:董*华', | ||
| 439 | - date: '2024-02-29 11:45', | ||
| 440 | - tag: '健康保障', | ||
| 441 | - status: 'generated', | ||
| 442 | - fileName: '家庭健康保障计划.pdf', | ||
| 443 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 444 | - }, | ||
| 445 | - { | ||
| 446 | - id: 29, | ||
| 447 | - title: '退休收入保障计划', | ||
| 448 | - client: '客户:潘*婷', | ||
| 449 | - date: '2024-02-28 15:20', | ||
| 450 | - tag: '养老规划', | ||
| 451 | - status: 'generated', | ||
| 452 | - fileName: '退休收入保障计划.pdf', | ||
| 453 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 454 | - }, | ||
| 455 | - { | ||
| 456 | - id: 30, | ||
| 457 | - title: '企业税务优化方案', | ||
| 458 | - client: '客户:袁*明', | ||
| 459 | - date: '2024-02-28 10:00', | ||
| 460 | - tag: '税务筹划', | ||
| 461 | - status: 'generated', | ||
| 462 | - fileName: '企业税务优化方案.pdf', | ||
| 463 | - downloadUrl: 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf' | ||
| 464 | - } | ||
| 465 | -]) | ||
| 466 | - | ||
| 467 | -/** | ||
| 468 | - * 获取当前 tab 的完整列表(过滤后的) | ||
| 469 | - */ | ||
| 470 | -const getFilteredList = () => { | ||
| 471 | - const currentTab = tabsData.value.find(tab => tab.id === activeTabId.value) | ||
| 472 | - if (!currentTab) { | ||
| 473 | - return [] | ||
| 474 | - } | ||
| 475 | - | ||
| 476 | - let result = currentTab.list | ||
| 477 | - | ||
| 478 | - // Filter by Search | ||
| 479 | - if (searchValue.value) { | ||
| 480 | - const key = searchValue.value.toLowerCase() | ||
| 481 | - result = result.filter(item => | ||
| 482 | - item.title.toLowerCase().includes(key) || | ||
| 483 | - item.client.toLowerCase().includes(key) | ||
| 484 | - ) | ||
| 485 | } | 177 | } |
| 486 | - | ||
| 487 | - return result | ||
| 488 | } | 178 | } |
| 489 | 179 | ||
| 490 | /** | 180 | /** |
| 491 | - * 加载计划书列表(模拟分页) | 181 | + * 加载计划书列表(调用真实 API) |
| 492 | - * @param {number} page - 页码(从0开始) | 182 | + * @param {number} page - 页码(从1开始,API 要求) |
| 493 | * @param {number} limit - 每页数量 | 183 | * @param {number} limit - 每页数量 |
| 494 | * @param {boolean} isLoadMore - 是否为加载更多 | 184 | * @param {boolean} isLoadMore - 是否为加载更多 |
| 495 | */ | 185 | */ |
| 496 | -const fetchPlanList = async (page = 0, limit = pageSize, isLoadMore = false) => { | 186 | +const fetchPlanList = async (page = 1, limit = pageSize, isLoadMore = false) => { |
| 497 | try { | 187 | try { |
| 498 | // 如果是加载更多,使用 loadingMore 状态,否则使用 loading 状态 | 188 | // 如果是加载更多,使用 loadingMore 状态,否则使用 loading 状态 |
| 499 | if (isLoadMore) { | 189 | if (isLoadMore) { |
| ... | @@ -502,33 +192,52 @@ const fetchPlanList = async (page = 0, limit = pageSize, isLoadMore = false) => | ... | @@ -502,33 +192,52 @@ const fetchPlanList = async (page = 0, limit = pageSize, isLoadMore = false) => |
| 502 | loading.value = true | 192 | loading.value = true |
| 503 | } | 193 | } |
| 504 | 194 | ||
| 505 | - // 模拟网络延迟(方便测试加载状态) | 195 | + // 构建请求参数 |
| 506 | - // await new Promise(resolve => setTimeout(resolve, 500)) | 196 | + const params = { |
| 197 | + page: page, | ||
| 198 | + limit: limit | ||
| 199 | + } | ||
| 507 | 200 | ||
| 508 | - // 获取过滤后的完整列表 | 201 | + // 根据 activeTabId 添加状态过滤 |
| 509 | - const fullList = getFilteredList() | 202 | + if (activeTabId.value !== '') { |
| 203 | + params.order_status = activeTabId.value | ||
| 204 | + } | ||
| 510 | 205 | ||
| 511 | - // 模拟分页:根据 page 和 limit 获取对应的数据 | 206 | + // 搜索关键字 |
| 512 | - const start = page * limit | 207 | + if (searchValue.value) { |
| 513 | - const end = start + limit | 208 | + params.keyword = searchValue.value |
| 209 | + } | ||
| 210 | + | ||
| 211 | + // 调用 API | ||
| 212 | + const res = await listAPI(params) | ||
| 514 | 213 | ||
| 515 | - const pageData = fullList.slice(start, end) | 214 | + if (res.code === 1 && res.data) { |
| 215 | + const apiList = res.data.list || [] | ||
| 216 | + const transformedList = apiList.map(transformApiItem) | ||
| 516 | 217 | ||
| 517 | if (isLoadMore) { | 218 | if (isLoadMore) { |
| 518 | // 加载更多:追加数据 | 219 | // 加载更多:追加数据 |
| 519 | - currentList.value = [...currentList.value, ...pageData] | 220 | + currentList.value = [...currentList.value, ...transformedList] |
| 520 | } else { | 221 | } else { |
| 521 | // 首次加载或刷新:替换数据 | 222 | // 首次加载或刷新:替换数据 |
| 522 | - currentList.value = pageData | 223 | + currentList.value = transformedList |
| 523 | } | 224 | } |
| 524 | 225 | ||
| 525 | // 判断是否还有更多数据 | 226 | // 判断是否还有更多数据 |
| 526 | // 如果返回的数据量少于请求的量,说明没有更多了 | 227 | // 如果返回的数据量少于请求的量,说明没有更多了 |
| 527 | - hasMore.value = pageData.length >= limit | 228 | + hasMore.value = transformedList.length >= limit |
| 229 | + } else { | ||
| 230 | + Taro.showToast({ | ||
| 231 | + title: res.msg || '加载失败', | ||
| 232 | + icon: 'none', | ||
| 233 | + duration: 2000 | ||
| 234 | + }) | ||
| 235 | + } | ||
| 528 | } catch (error) { | 236 | } catch (error) { |
| 237 | + console.error('加载计划书列表失败:', error) | ||
| 529 | Taro.showToast({ | 238 | Taro.showToast({ |
| 530 | - title: '加载失败', | 239 | + title: '网络异常,请重试', |
| 531 | - icon: 'error', | 240 | + icon: 'none', |
| 532 | duration: 2000 | 241 | duration: 2000 |
| 533 | }) | 242 | }) |
| 534 | } finally { | 243 | } finally { |
| ... | @@ -541,20 +250,6 @@ const fetchPlanList = async (page = 0, limit = pageSize, isLoadMore = false) => | ... | @@ -541,20 +250,6 @@ const fetchPlanList = async (page = 0, limit = pageSize, isLoadMore = false) => |
| 541 | } | 250 | } |
| 542 | 251 | ||
| 543 | /** | 252 | /** |
| 544 | - * 初始化数据分布 | ||
| 545 | - * @description 根据分类规则将 allList 中的数据分配到各个 tab 中 | ||
| 546 | - */ | ||
| 547 | -const initTabsData = () => { | ||
| 548 | - tabsData.value.forEach((tab) => { | ||
| 549 | - if (tab.id === '') { | ||
| 550 | - tab.list = [...allList.value] | ||
| 551 | - } else { | ||
| 552 | - tab.list = allList.value.filter(item => item.status === tab.id) | ||
| 553 | - } | ||
| 554 | - }) | ||
| 555 | -} | ||
| 556 | - | ||
| 557 | -/** | ||
| 558 | * Tab 点击处理 | 253 | * Tab 点击处理 |
| 559 | */ | 254 | */ |
| 560 | const onTabClick = (id) => { | 255 | const onTabClick = (id) => { |
| ... | @@ -565,8 +260,8 @@ const onTabClick = (id) => { | ... | @@ -565,8 +260,8 @@ const onTabClick = (id) => { |
| 565 | activeTabId.value = id | 260 | activeTabId.value = id |
| 566 | listVisible.value = false | 261 | listVisible.value = false |
| 567 | 262 | ||
| 568 | - // 重置分页状态 | 263 | + // 重置分页状态(从第 1 页开始) |
| 569 | - currentPage.value = 0 | 264 | + currentPage.value = 1 |
| 570 | hasMore.value = true | 265 | hasMore.value = true |
| 571 | 266 | ||
| 572 | nextTick(() => { | 267 | nextTick(() => { |
| ... | @@ -574,7 +269,7 @@ const onTabClick = (id) => { | ... | @@ -574,7 +269,7 @@ const onTabClick = (id) => { |
| 574 | listVisible.value = true | 269 | listVisible.value = true |
| 575 | 270 | ||
| 576 | // 重新加载数据 | 271 | // 重新加载数据 |
| 577 | - fetchPlanList(0, pageSize, false) | 272 | + fetchPlanList(1, pageSize, false) |
| 578 | }) | 273 | }) |
| 579 | } | 274 | } |
| 580 | 275 | ||
| ... | @@ -582,14 +277,14 @@ const onTabClick = (id) => { | ... | @@ -582,14 +277,14 @@ const onTabClick = (id) => { |
| 582 | * 搜索处理 | 277 | * 搜索处理 |
| 583 | */ | 278 | */ |
| 584 | const onSearch = () => { | 279 | const onSearch = () => { |
| 585 | - // 重置分页状态 | 280 | + // 重置分页状态(从第 1 页开始) |
| 586 | - currentPage.value = 0 | 281 | + currentPage.value = 1 |
| 587 | hasMore.value = true | 282 | hasMore.value = true |
| 588 | 283 | ||
| 589 | listRenderKey.value += 1 | 284 | listRenderKey.value += 1 |
| 590 | 285 | ||
| 591 | // 重新加载数据 | 286 | // 重新加载数据 |
| 592 | - fetchPlanList(0, pageSize, false) | 287 | + fetchPlanList(1, pageSize, false) |
| 593 | } | 288 | } |
| 594 | 289 | ||
| 595 | /** | 290 | /** |
| ... | @@ -601,14 +296,14 @@ const onClear = () => { | ... | @@ -601,14 +296,14 @@ const onClear = () => { |
| 601 | // 清空搜索值 | 296 | // 清空搜索值 |
| 602 | searchValue.value = '' | 297 | searchValue.value = '' |
| 603 | 298 | ||
| 604 | - // 重置分页状态 | 299 | + // 重置分页状态(从第 1 页开始) |
| 605 | - currentPage.value = 0 | 300 | + currentPage.value = 1 |
| 606 | hasMore.value = true | 301 | hasMore.value = true |
| 607 | 302 | ||
| 608 | listRenderKey.value += 1 | 303 | listRenderKey.value += 1 |
| 609 | 304 | ||
| 610 | // 重新加载数据 | 305 | // 重新加载数据 |
| 611 | - fetchPlanList(0, pageSize, false) | 306 | + fetchPlanList(1, pageSize, false) |
| 612 | 307 | ||
| 613 | console.log('[Plan Page] onClear 完成,列表已刷新') | 308 | console.log('[Plan Page] onClear 完成,列表已刷新') |
| 614 | } | 309 | } |
| ... | @@ -617,11 +312,8 @@ const onClear = () => { | ... | @@ -617,11 +312,8 @@ const onClear = () => { |
| 617 | * 页面加载时初始化数据 | 312 | * 页面加载时初始化数据 |
| 618 | */ | 313 | */ |
| 619 | useLoad(() => { | 314 | useLoad(() => { |
| 620 | - // 初始化数据分布 | ||
| 621 | - initTabsData() | ||
| 622 | - | ||
| 623 | // 加载第一页数据 | 315 | // 加载第一页数据 |
| 624 | - fetchPlanList(0, pageSize, false) | 316 | + fetchPlanList(1, pageSize, false) |
| 625 | }) | 317 | }) |
| 626 | 318 | ||
| 627 | /** | 319 | /** |
| ... | @@ -678,25 +370,28 @@ const onView = async (item) => { | ... | @@ -678,25 +370,28 @@ const onView = async (item) => { |
| 678 | 370 | ||
| 679 | /** | 371 | /** |
| 680 | * 删除计划书 | 372 | * 删除计划书 |
| 373 | + * @description 注意:当前后端可能未提供删除接口,暂时只做前端提示 | ||
| 681 | */ | 374 | */ |
| 682 | const onDelete = (item) => { | 375 | const onDelete = (item) => { |
| 683 | Taro.showModal({ | 376 | Taro.showModal({ |
| 684 | title: '提示', | 377 | title: '提示', |
| 685 | - content: '确定要删除该计划书吗?', | 378 | + content: '删除功能需要后端支持,是否继续?', |
| 686 | success: (res) => { | 379 | success: (res) => { |
| 687 | if (res.confirm) { | 380 | if (res.confirm) { |
| 688 | - // 从 allList 中删除 | 381 | + // TODO: 调用删除 API |
| 689 | - const index = allList.value.findIndex(i => i.id === item.id) | 382 | + // const res = await deletePlanAPI({ id: item.id }) |
| 690 | - if (index !== -1) { | 383 | + // if (res.code === 1) { |
| 691 | - allList.value.splice(index, 1) | 384 | + // Taro.showToast({ title: '已删除', icon: 'success' }) |
| 692 | - // 重新初始化 tabsData | 385 | + // // 重新加载列表 |
| 693 | - initTabsData() | 386 | + // currentPage.value = 1 |
| 694 | - // 重置分页状态并重新加载 | 387 | + // fetchPlanList(1, pageSize, false) |
| 695 | - currentPage.value = 0 | 388 | + // } |
| 696 | - hasMore.value = true | 389 | + |
| 697 | - fetchPlanList(0, pageSize, false) | 390 | + Taro.showToast({ |
| 698 | - Taro.showToast({ title: '已删除', icon: 'success' }) | 391 | + title: '删除功能待实现', |
| 699 | - } | 392 | + icon: 'none', |
| 393 | + duration: 2000 | ||
| 394 | + }) | ||
| 700 | } | 395 | } |
| 701 | } | 396 | } |
| 702 | }) | 397 | }) | ... | ... |
-
Please register or login to post a comment