# Spec: 第四步 - 物流发货信息功能 ## ADDED Requirements ### 发货信息表单 - **Requirement**: 实现完整的发货信息填写表单,包含收件人、物流公司、运单号等必要信息 - **Scenario**: 用户进入第四步后可以看到发货信息表单,需要填写完整的收货地址、联系方式和物流信息才能提交 ### 表单验证 - **Requirement**: 实现表单字段的完整验证机制,包括必填字段验证、格式验证和业务规则验证 - **Scenario**: 用户填写表单时能够实时看到验证反馈,提交前进行完整检查,确保数据完整性和正确性 ### 发货完成流程 - **Requirement**: 实现发货信息提交后的完整流程,包括状态更新、确认反馈和流程完成 - **Scenario**: 用户确认发货信息后,系统处理发货请求,更新订单状态,并提供发货完成的确认反馈 ### 返回上一步功能 - **Requirement**: 添加返回上一步的导航功能,允许用户修改前面步骤的信息 - **Scenario**: 用户在发货页面可以点击返回按钮修改模型或拆件结果,已填写的发货信息会被保留 ### 表单数据持久化 - **Requirement**: 实现表单数据的本地持久化,防止页面刷新导致数据丢失 - **Scenario**: 用户在填写发货信息过程中刷新页面,已填写的数据能够自动恢复 ## 技术实现细节 ### 布局结构 ```html

{{ t('disassembly.steps.shipping') }}

{{ t('disassembly.status.current') }}
{{ t('disassembly.actions.backToModel') }} {{ t('disassembly.actions.submitShipping') }}
``` ### 表单数据结构和验证 ```javascript const shippingForm = reactive({ recipient: '', phone: '', address: '', logistics: '', trackingNumber: '', remarks: '' }) const shippingRules = { recipient: [ { required: true, message: t('disassembly.validation.recipientRequired'), trigger: 'blur' }, { min: 2, max: 20, message: t('disassembly.validation.recipientLength'), trigger: 'blur' } ], phone: [ { required: true, message: t('disassembly.validation.phoneRequired'), trigger: 'blur' }, { pattern: /^1[3-9]\d{9}$/, message: t('disassembly.validation.phoneFormat'), trigger: 'blur' } ], address: [ { required: true, message: t('disassembly.validation.addressRequired'), trigger: 'blur' }, { min: 10, max: 200, message: t('disassembly.validation.addressLength'), trigger: 'blur' } ], logistics: [ { required: true, message: t('disassembly.validation.logisticsRequired'), trigger: 'change' } ], trackingNumber: [ { required: true, message: t('disassembly.validation.trackingNumberRequired'), trigger: 'blur' }, { pattern: /^[a-zA-Z0-9]{8,20}$/, message: t('disassembly.validation.trackingNumberFormat'), trigger: 'blur' } ] } ``` ### 表单提交逻辑 ```javascript const submitShipping = async () => { try { // 表单验证 const valid = await shippingFormRef.value.validate() if (!valid) return submitLoading.value = true // 调用发货API(用户后续实现) // const result = await submitShippingAPI(contentId, shippingForm) // 模拟提交过程 await new Promise(resolve => setTimeout(resolve, 2000)) // 更新步骤状态 updateStepStatus(4, 'completed') workflowState.contentData.shippingInfo = { ...shippingForm } // 显示成功消息 ElMessage.success(t('disassembly.success.shippingSubmitted')) // 跳转到完成页面或返回列表 setTimeout(() => { router.push('/admin/orders/content-review') }, 2000) } catch (error) { ElMessage.error(t('disassembly.errors.shippingSubmitFailed')) } finally { submitLoading.value = false } } const goToPreviousStep = () => { workflowState.currentStep = 3 workflowState.stepStatus[4] = 'pending' } ``` ### 数据持久化 ```javascript // 保存表单数据到sessionStorage const saveFormData = () => { sessionStorage.setItem('shippingFormData', JSON.stringify(shippingForm)) } // 从sessionStorage恢复表单数据 const restoreFormData = () => { const savedData = sessionStorage.getItem('shippingFormData') if (savedData) { try { const parsed = JSON.parse(savedData) Object.assign(shippingForm, parsed) } catch (error) { console.error('Failed to restore form data:', error) } } } // 监听表单变化自动保存 watch(shippingForm, saveFormData, { deep: true }) // 组件挂载时恢复数据 onMounted(() => { restoreFormData() }) ``` ### 计算属性 ```javascript const shippingFormValid = computed(() => { return shippingForm.recipient && shippingForm.phone && shippingForm.address && shippingForm.logistics && shippingForm.trackingNumber }) ``` ### 样式实现 ```css .shipping-form-container { background: #ffffff; border-radius: 12px; padding: 24px; margin-bottom: 32px; box-shadow: 0 2px 8px rgba(107, 70, 193, 0.1); } .form-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 32px; padding-bottom: 16px; border-bottom: 1px solid #e5e7eb; } .form-header h3 { margin: 0; color: #1f2937; font-size: 18px; font-weight: 600; } .shipping-form { max-width: 600px; } .shipping-form .el-form-item { margin-bottom: 24px; } .shipping-form .el-form-item__label { font-weight: 500; color: #374151; } .step-actions { display: flex; gap: 16px; justify-content: space-between; padding-top: 24px; border-top: 1px solid #e5e7eb; } .step-actions .el-button:first-child { margin-right: auto; } .step-actions .el-button:last-child { min-width: 120px; } /* 响应式设计 */ @media (max-width: 768px) { .shipping-form-container { padding: 16px; } .shipping-form { max-width: 100%; } .shipping-form .el-form-item { margin-bottom: 16px; } .step-actions { flex-direction: column; gap: 12px; } .step-actions .el-button { width: 100%; } .step-actions .el-button:first-child { margin-right: 0; order: 2; } .step-actions .el-button:last-child { order: 1; } } /* 表单验证样式 */ .el-form-item.is-error .el-input__wrapper, .el-form-item.is-error .el-textarea__inner { border-color: #f56c6c; } .el-form-item__error { color: #f56c6c; font-size: 12px; margin-top: 4px; } /* 成功状态样式 */ .shipping-form-container.completed { background: #f0f9ff; border: 1px solid #0ea5e9; } ``` ### 自动填充逻辑 ```javascript // 如果用户之前有过发货记录,可以自动填充部分信息 const autoFillFromHistory = async () => { try { // 调用历史记录API(用户后续实现) // const history = await getShippingHistory(userId) // 模拟获取历史数据 const history = { recipient: '张三', phone: '138****8888', address: '北京市朝阳区***街道**号', logistics: 'sf' } // 只填充非空的历史数据 Object.keys(history).forEach(key => { if (history[key] && !shippingForm[key]) { shippingForm[key] = history[key] } }) } catch (error) { console.error('Failed to auto-fill from history:', error) } } ``` ## 验证标准 - 发货表单能够正确显示和提交 - 表单验证规则能够正确工作 - 返回上一步功能正常 - 表单数据持久化功能完整 - 响应式布局在各种设备上表现良好 - 发货完成流程正常