feat(order): 支持东南亚多国地址格式的订单创建功能
- 实现动态地址表单,根据选择国家显示对应字段 - 添加新加坡组屋号、单元号,马来西亚州属,菲律宾Barangay等特殊字段 - 集成泰国双语地址、越南省市坊三级地址格式支持 - 优化订单确认页地址展示,按从小到大格式排列 - 添加邮编格式验证和自动匹配城市功能 - 实现SKU货币自动识别国家并预设字段 - 重构README文档结构和项目说明信息
This commit is contained in:
@@ -1,130 +0,0 @@
|
||||
# 商品详情页说明
|
||||
|
||||
## 页面功能
|
||||
|
||||
### 1. 商品展示区域
|
||||
- **左侧大图**:支持点击放大预览
|
||||
- **缩略图列表**:点击切换主图
|
||||
- **图片预览**:Element Plus图片预览功能
|
||||
|
||||
### 2. 商品信息区域
|
||||
- **商品标题**:大字体,醒目展示
|
||||
- **商品副标题**:补充说明信息
|
||||
- **价格展示**:
|
||||
- 现价(大字体,红色高亮)
|
||||
- 原价(删除线,灰色)
|
||||
- 节省金额提示
|
||||
|
||||
### 3. 规格选择
|
||||
- **多规格支持**:颜色、尺寸等
|
||||
- **可视化选择**:标签式选择,选中高亮
|
||||
- **必选验证**:支付前验证所有规格已选择
|
||||
|
||||
### 4. 数量选择
|
||||
- **数量输入框**:支持增减
|
||||
- **库存显示**:实时显示库存信息
|
||||
- **库存限制**:超过库存时禁用
|
||||
|
||||
### 5. 服务保障
|
||||
- **7天无理由退货**
|
||||
- **正品保证**
|
||||
- **极速发货**
|
||||
|
||||
### 6. 操作按钮
|
||||
- **立即购买**:橙色渐变按钮
|
||||
- **立即支付**:红色渐变按钮(主要支付按钮)
|
||||
- **悬停效果**:按钮有阴影和上浮动画
|
||||
|
||||
### 7. 商品详情标签页
|
||||
- **商品详情**:HTML格式的商品描述
|
||||
- **规格参数**:表格形式展示参数
|
||||
- **用户评价**:评价列表展示
|
||||
|
||||
## 样式特点
|
||||
|
||||
### 参考主流电商设计
|
||||
1. **价格突出**:大字体、红色、渐变背景
|
||||
2. **图片展示**:大图+缩略图,支持预览
|
||||
3. **按钮设计**:渐变背景、阴影效果、悬停动画
|
||||
4. **卡片布局**:圆角、阴影、层次分明
|
||||
5. **响应式设计**:移动端自适应
|
||||
|
||||
### 颜色方案
|
||||
- **主色调**:红色系(#f56c6c, #ff4757)
|
||||
- **辅助色**:橙色系(#ff9500, #ff6b00)
|
||||
- **背景色**:浅灰色(#f5f7fa)
|
||||
- **文字色**:深灰色(#333, #666)
|
||||
|
||||
## 支付流程
|
||||
|
||||
1. 用户选择商品规格和数量
|
||||
2. 点击"立即支付"按钮
|
||||
3. 系统自动生成订单号
|
||||
4. 调用后端API创建支付订单
|
||||
5. 跳转到收银台页面完成支付
|
||||
|
||||
## 数据格式
|
||||
|
||||
### 商品数据结构
|
||||
```javascript
|
||||
{
|
||||
id: '1',
|
||||
name: '商品名称',
|
||||
subtitle: '商品副标题',
|
||||
price: 20.00,
|
||||
originalPrice: 25.00,
|
||||
currency: 'USD',
|
||||
stock: 100,
|
||||
images: ['图片URL数组'],
|
||||
specs: [
|
||||
{
|
||||
name: '规格名称',
|
||||
options: [
|
||||
{ label: '选项标签', value: '选项值' }
|
||||
]
|
||||
}
|
||||
],
|
||||
description: 'HTML格式的商品描述',
|
||||
params: [
|
||||
{ name: '参数名', value: '参数值' }
|
||||
],
|
||||
reviews: [
|
||||
{
|
||||
user: '用户名',
|
||||
rating: 5,
|
||||
date: '日期',
|
||||
content: '评价内容'
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 路由配置
|
||||
|
||||
- **路径**:`/` 或 `/product/:id`
|
||||
- **组件**:`ProductDetail.vue`
|
||||
- **参数**:`id` - 商品ID(可选)
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 访问商品详情页
|
||||
```
|
||||
http://localhost:3000/
|
||||
http://localhost:3000/product/1
|
||||
```
|
||||
|
||||
### 从其他页面跳转
|
||||
```javascript
|
||||
router.push('/product/1')
|
||||
router.push({ path: '/product', params: { id: '1' } })
|
||||
```
|
||||
|
||||
## 扩展建议
|
||||
|
||||
1. **商品列表页**:创建商品列表,点击跳转到详情页
|
||||
2. **购物车功能**:添加购物车按钮和功能
|
||||
3. **商品推荐**:在详情页下方展示相关商品
|
||||
4. **图片轮播**:主图支持自动轮播
|
||||
5. **视频展示**:支持商品视频播放
|
||||
6. **分享功能**:添加商品分享功能
|
||||
|
||||
130
README.md
130
README.md
@@ -1,6 +1,6 @@
|
||||
# MT Pay Frontend
|
||||
|
||||
PingPong支付系统前端页面(Vue3)
|
||||
电商支付系统前端(Vue 3 + Element Plus)
|
||||
|
||||
## 技术栈
|
||||
|
||||
@@ -10,6 +10,23 @@ PingPong支付系统前端页面(Vue3)
|
||||
- Axios
|
||||
- Vite
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 安装依赖
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 启动开发服务器
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 构建生产版本
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
@@ -17,99 +34,50 @@ MTKJPAY-FRONT/
|
||||
├── src/
|
||||
│ ├── api/ # API接口
|
||||
│ │ ├── request.js # Axios封装
|
||||
│ │ └── payment.js # 支付相关API
|
||||
│ │ ├── product.js # 商品API
|
||||
│ │ └── order.js # 订单API
|
||||
│ ├── router/ # 路由配置
|
||||
│ │ └── index.js
|
||||
│ ├── views/ # 页面组件
|
||||
│ │ ├── CreateOrder.vue # 创建订单页面
|
||||
│ │ ├── Checkout.vue # 收银台页面
|
||||
│ │ ├── PaymentResult.vue # 支付结果页面
|
||||
│ │ └── OrderQuery.vue # 订单查询页面
|
||||
│ │ ├── ProductDetail.vue # 商品详情页
|
||||
│ │ ├── CreateOrder.vue # 创建订单页(支持东南亚地址)
|
||||
│ │ ├── OrderConfirm.vue # 订单确认页
|
||||
│ │ └── OrderQuery.vue # 订单查询页
|
||||
│ ├── utils/ # 工具函数
|
||||
│ │ ├── helpers.js # 辅助函数
|
||||
│ │ └── countryConfig.js # 国家配置
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.js # 入口文件
|
||||
├── index.html # HTML模板
|
||||
├── vite.config.js # Vite配置
|
||||
└── package.json # 项目配置
|
||||
└── index.html # HTML模板
|
||||
```
|
||||
|
||||
## 安装依赖
|
||||
## 核心功能
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
- 商品详情展示(支持多图、SKU选择、货币切换)
|
||||
- 订单创建(动态地址表单,根据国家显示不同字段)
|
||||
- 订单确认(显示货币转换信息)
|
||||
- PayPal支付集成
|
||||
|
||||
## 开发运行
|
||||
## 地址表单
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
支持东南亚国家地址格式:
|
||||
- 新加坡:组屋号、单元号
|
||||
- 马来西亚:州属
|
||||
- 菲律宾:Barangay
|
||||
- 泰国:泰文地址(双语)
|
||||
- 越南:省/市/郡/区/坊
|
||||
|
||||
访问:http://localhost:3000
|
||||
根据SKU货币自动识别国家,动态显示对应字段。
|
||||
|
||||
## 构建生产版本
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 功能说明
|
||||
|
||||
### 1. 创建订单页面 (/)
|
||||
- 填写订单信息(金额、币种、交易类型等)
|
||||
- 填写风控信息(客户信息、商品信息、地址信息等)
|
||||
- 提交后跳转到收银台
|
||||
|
||||
### 2. 收银台页面 (/checkout)
|
||||
- 集成PingPong支付SDK
|
||||
- 用户完成支付操作
|
||||
- 支付完成后跳转到结果页面
|
||||
|
||||
### 3. 支付结果页面 (/result)
|
||||
- 显示支付结果
|
||||
- 显示订单详情
|
||||
- 提供继续支付和查询订单操作
|
||||
|
||||
### 4. 订单查询页面 (/query)
|
||||
- 根据商户订单号查询订单状态
|
||||
- 显示订单详情
|
||||
- 支持继续支付(如果订单未完成)
|
||||
|
||||
## 配置说明
|
||||
|
||||
### API代理配置
|
||||
|
||||
在 `vite.config.js` 中配置了API代理:
|
||||
## API配置
|
||||
|
||||
在 `src/api/request.js` 中配置后端API地址:
|
||||
```javascript
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8080', // 后端服务地址
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
const service = axios.create({
|
||||
baseURL: 'http://localhost:8082/api',
|
||||
timeout: 10000
|
||||
})
|
||||
```
|
||||
|
||||
### PingPong SDK模式
|
||||
|
||||
在 `Checkout.vue` 中配置SDK模式:
|
||||
|
||||
```javascript
|
||||
mode: 'sandbox' // 根据环境修改:sandbox/test/build
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **后端服务**:确保后端服务已启动(默认端口8080)
|
||||
2. **CORS配置**:如果跨域,需要后端配置CORS
|
||||
3. **SDK模式**:生产环境需要将 `mode` 改为 `build`
|
||||
4. **重定向URL**:创建订单时的重定向URL会自动设置为当前域名
|
||||
|
||||
## 开发建议
|
||||
|
||||
1. 根据实际需求调整表单字段
|
||||
2. 根据业务需求添加更多验证规则
|
||||
3. 优化用户体验和错误提示
|
||||
4. 添加加载状态和错误处理
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
|
||||
@@ -74,11 +74,12 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-divider>收货地址</el-divider>
|
||||
<el-alert v-if="currentCountryConfig" :title="`地址格式:${currentCountryConfig.addressFormat}`" type="info" :closable="false" style="margin-bottom: 20px" />
|
||||
|
||||
<el-form-item label="收货人姓名" prop="shippingName">
|
||||
<el-input
|
||||
v-model="form.shippingName"
|
||||
placeholder="请输入收货人姓名"
|
||||
placeholder="请输入收货人姓名(需与证件一致,支持当地语言+英文)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -86,42 +87,172 @@
|
||||
<el-form-item label="收货人电话" prop="shippingPhone">
|
||||
<el-input
|
||||
v-model="form.shippingPhone"
|
||||
placeholder="请输入收货人电话"
|
||||
:placeholder="currentCountryConfig ? `请输入收货人电话(国际区号:${currentCountryConfig.phoneCode})` : '请输入收货人电话(带国际区号)'"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="收货国家" prop="shippingCountry">
|
||||
<el-select v-model="form.shippingCountry" placeholder="请选择国家" style="width: 100%">
|
||||
<el-option label="中国 (CN)" value="CN" />
|
||||
<el-option label="美国 (US)" value="US" />
|
||||
<el-option label="新加坡 (SG)" value="SG" />
|
||||
<el-option label="马来西亚 (MY)" value="MY" />
|
||||
<el-option label="菲律宾 (PH)" value="PH" />
|
||||
<el-option label="泰国 (TH)" value="TH" />
|
||||
<el-option label="越南 (VN)" value="VN" />
|
||||
<el-option label="新加坡 (SG)" value="SG" />
|
||||
<el-option label="中国 (CN)" value="CN" />
|
||||
<el-option label="美国 (US)" value="US" />
|
||||
<el-option label="英国 (GB)" value="GB" />
|
||||
<el-option label="德国 (DE)" value="DE" />
|
||||
<el-option label="法国 (FR)" value="FR" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="收货城市" prop="shippingCity">
|
||||
<!-- 详细地址1(门牌号、街道、楼栋)- 所有国家都显示 -->
|
||||
<el-form-item v-if="showField('shippingAddressLine1')" :label="getFieldLabel('shippingAddressLine1')" prop="shippingAddressLine1">
|
||||
<el-input
|
||||
v-model="form.shippingAddressLine1"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入门牌号、街道、楼栋"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 详细地址2(楼层、单元号,可选) -->
|
||||
<el-form-item v-if="showField('shippingAddressLine2')" :label="getFieldLabel('shippingAddressLine2')" prop="shippingAddressLine2">
|
||||
<el-input
|
||||
v-model="form.shippingAddressLine2"
|
||||
placeholder="请输入楼层、单元号(可选)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 新加坡:组屋号和单元号 -->
|
||||
<template v-if="form.shippingCountry === 'SG'">
|
||||
<el-form-item :label="getFieldLabel('shippingBlockNumber')" prop="shippingBlockNumber">
|
||||
<el-input
|
||||
v-model="form.shippingBlockNumber"
|
||||
placeholder="例如:Blk 123"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="getFieldLabel('shippingUnitNumber')" prop="shippingUnitNumber">
|
||||
<el-input
|
||||
v-model="form.shippingUnitNumber"
|
||||
placeholder="例如:#01-234"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 菲律宾:Barangay -->
|
||||
<el-form-item v-if="form.shippingCountry === 'PH'" :label="getFieldLabel('shippingBarangay')" prop="shippingBarangay">
|
||||
<el-input
|
||||
v-model="form.shippingBarangay"
|
||||
placeholder="请输入Barangay(社区编号)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 泰国:泰文地址 -->
|
||||
<el-form-item v-if="form.shippingCountry === 'TH'" :label="getFieldLabel('shippingAddressThai')" prop="shippingAddressThai">
|
||||
<el-input
|
||||
v-model="form.shippingAddressThai"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入泰文地址(支持双语)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 越南:省、市/郡、区/坊 -->
|
||||
<template v-if="form.shippingCountry === 'VN'">
|
||||
<el-form-item :label="getFieldLabel('shippingProvince')" prop="shippingProvince">
|
||||
<el-input
|
||||
v-model="form.shippingProvince"
|
||||
placeholder="请输入省 (Tỉnh)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="getFieldLabel('shippingDistrict')" prop="shippingDistrict">
|
||||
<el-input
|
||||
v-model="form.shippingDistrict"
|
||||
placeholder="请输入市/郡 (Thành phố/Huyện)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="getFieldLabel('shippingWard')" prop="shippingWard">
|
||||
<el-input
|
||||
v-model="form.shippingWard"
|
||||
placeholder="请输入区/坊 (Quận/Phường)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 马来西亚:州属 -->
|
||||
<el-form-item v-if="form.shippingCountry === 'MY'" :label="getFieldLabel('shippingStateMalaysia')" prop="shippingStateMalaysia">
|
||||
<el-input
|
||||
v-model="form.shippingStateMalaysia"
|
||||
placeholder="例如:Selangor(雪兰莪)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 泰国:行政区域(区/Tambon) -->
|
||||
<el-form-item v-if="form.shippingCountry === 'TH' && showField('shippingAdministrativeArea')" :label="getFieldLabel('shippingAdministrativeArea')" prop="shippingAdministrativeArea">
|
||||
<el-input
|
||||
v-model="form.shippingAdministrativeArea"
|
||||
placeholder="请输入区 (Tambon)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 城市和州/省(通用字段) -->
|
||||
<el-form-item label="城市/城镇" prop="shippingCity">
|
||||
<el-input
|
||||
v-model="form.shippingCity"
|
||||
placeholder="请输入收货城市"
|
||||
:placeholder="form.shippingCountry === 'TH' ? '请输入县 (Amphoe)' : '请输入城市/城镇'"
|
||||
style="width: 48%"
|
||||
clearable
|
||||
/>
|
||||
<el-input
|
||||
v-if="form.shippingCountry !== 'VN' && form.shippingCountry !== 'MY'"
|
||||
v-model="form.shippingState"
|
||||
placeholder="州/省(可选)"
|
||||
:placeholder="form.shippingCountry === 'TH' ? '府 (Changwat)' : form.shippingCountry === 'PH' ? '省 (Province)' : '州/省(可选)'"
|
||||
style="width: 48%; margin-left: 4%"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="街道地址" prop="shippingStreet">
|
||||
<!-- 邮编 -->
|
||||
<el-form-item label="邮政编码" prop="shippingPostcode">
|
||||
<el-input
|
||||
v-model="form.shippingPostcode"
|
||||
:placeholder="currentCountryConfig ? `请输入邮编(${currentCountryConfig.postcodeLength}位数字)` : '请输入邮编'"
|
||||
clearable
|
||||
style="width: 48%"
|
||||
>
|
||||
<template #append v-if="postcodeMatching">
|
||||
<el-icon class="is-loading"><el-icon-loading /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<span v-if="currentCountryConfig" style="margin-left: 10px; color: #909399; font-size: 12px">
|
||||
{{ currentCountryConfig.name }}邮编为{{ currentCountryConfig.postcodeLength }}位数字
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 楼层/单元/代收点(补充信息) -->
|
||||
<el-form-item label="楼层/单元/代收点" prop="shippingFloorUnit">
|
||||
<el-input
|
||||
v-model="form.shippingFloorUnit"
|
||||
placeholder="楼层、单元号或代收点信息(可选)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 兼容旧字段:街道地址(如果新字段为空,使用旧字段) -->
|
||||
<el-form-item v-if="!form.shippingAddressLine1" label="街道地址" prop="shippingStreet">
|
||||
<el-input
|
||||
v-model="form.shippingStreet"
|
||||
type="textarea"
|
||||
@@ -131,14 +262,6 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="邮编" prop="shippingPostcode">
|
||||
<el-input
|
||||
v-model="form.shippingPostcode"
|
||||
placeholder="请输入邮编(可选)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="订单备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
@@ -161,17 +284,19 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { createCustomerOrder } from '../api/order'
|
||||
import { formatAmount } from '../utils/helpers'
|
||||
import { getCountryConfig, getCountryByCurrency, validatePostcode, getRequiredFields } from '../utils/countryConfig'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const formRef = ref()
|
||||
const loading = ref(false)
|
||||
const productInfo = ref(null)
|
||||
const postcodeMatching = ref(false) // 邮编匹配中
|
||||
|
||||
const form = reactive({
|
||||
customerName: '',
|
||||
@@ -184,38 +309,225 @@ const form = reactive({
|
||||
shippingCity: '',
|
||||
shippingStreet: '',
|
||||
shippingPostcode: '',
|
||||
// 东南亚地址扩展字段
|
||||
shippingAddressLine1: '',
|
||||
shippingAddressLine2: '',
|
||||
shippingAdministrativeArea: '',
|
||||
shippingBlockNumber: '',
|
||||
shippingUnitNumber: '',
|
||||
shippingBarangay: '',
|
||||
shippingAddressThai: '',
|
||||
shippingProvince: '',
|
||||
shippingDistrict: '',
|
||||
shippingWard: '',
|
||||
shippingStateMalaysia: '',
|
||||
shippingFloorUnit: '',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const rules = {
|
||||
customerName: [
|
||||
{ required: true, message: '请输入客户姓名', trigger: 'blur' }
|
||||
],
|
||||
customerPhone: [
|
||||
{ required: true, message: '请输入客户电话', trigger: 'blur' },
|
||||
{ pattern: /^[0-9+\-\s()]+$/, message: '请输入有效的电话号码', trigger: 'blur' }
|
||||
],
|
||||
customerEmail: [
|
||||
{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }
|
||||
],
|
||||
shippingName: [
|
||||
{ required: true, message: '请输入收货人姓名', trigger: 'blur' }
|
||||
],
|
||||
shippingPhone: [
|
||||
{ required: true, message: '请输入收货人电话', trigger: 'blur' },
|
||||
{ pattern: /^[0-9+\-\s()]+$/, message: '请输入有效的电话号码', trigger: 'blur' }
|
||||
],
|
||||
shippingCountry: [
|
||||
{ required: true, message: '请选择收货国家', trigger: 'change' }
|
||||
],
|
||||
shippingCity: [
|
||||
{ required: true, message: '请输入收货城市', trigger: 'blur' }
|
||||
],
|
||||
shippingStreet: [
|
||||
{ required: true, message: '请输入街道地址', trigger: 'blur' }
|
||||
]
|
||||
// 当前国家配置
|
||||
const currentCountryConfig = computed(() => {
|
||||
return getCountryConfig(form.shippingCountry)
|
||||
})
|
||||
|
||||
// 是否显示特定字段
|
||||
const showField = (fieldName) => {
|
||||
if (!currentCountryConfig.value) {
|
||||
// 默认显示基础字段
|
||||
return ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
||||
'shippingState', 'shippingStreet', 'shippingPostcode'].includes(fieldName)
|
||||
}
|
||||
|
||||
const specialFields = currentCountryConfig.value.specialFields || []
|
||||
const requiredFields = currentCountryConfig.value.requiredFields || []
|
||||
|
||||
// 如果是特殊字段,只在对应国家显示
|
||||
if (specialFields.includes(fieldName)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 如果是必填字段,显示
|
||||
if (requiredFields.includes(fieldName)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 基础字段始终显示
|
||||
const baseFields = ['shippingName', 'shippingPhone', 'shippingCountry',
|
||||
'shippingCity', 'shippingState', 'shippingStreet',
|
||||
'shippingPostcode', 'shippingAddressLine1', 'shippingAddressLine2']
|
||||
if (baseFields.includes(fieldName)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 越南特殊字段
|
||||
if (form.shippingCountry === 'VN') {
|
||||
return ['shippingProvince', 'shippingDistrict', 'shippingWard'].includes(fieldName)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 获取字段标签
|
||||
const getFieldLabel = (fieldName) => {
|
||||
if (currentCountryConfig.value && currentCountryConfig.value.fieldLabels) {
|
||||
return currentCountryConfig.value.fieldLabels[fieldName] || fieldName
|
||||
}
|
||||
return fieldName
|
||||
}
|
||||
|
||||
// 监听国家变化,清空相关字段
|
||||
watch(() => form.shippingCountry, (newCountry, oldCountry) => {
|
||||
if (newCountry !== oldCountry) {
|
||||
// 清空国家特定字段
|
||||
form.shippingStateMalaysia = ''
|
||||
form.shippingBarangay = ''
|
||||
form.shippingBlockNumber = ''
|
||||
form.shippingUnitNumber = ''
|
||||
form.shippingAddressThai = ''
|
||||
form.shippingProvince = ''
|
||||
form.shippingDistrict = ''
|
||||
form.shippingWard = ''
|
||||
form.shippingAdministrativeArea = ''
|
||||
|
||||
// 更新电话区号提示
|
||||
if (currentCountryConfig.value) {
|
||||
form.shippingPhone = currentCountryConfig.value.phoneCode + ' '
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 监听邮编变化,自动匹配城市
|
||||
watch(() => form.shippingPostcode, async (newPostcode) => {
|
||||
if (!newPostcode || !form.shippingCountry) return
|
||||
|
||||
// 验证邮编格式
|
||||
if (!validatePostcode(form.shippingCountry, newPostcode)) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: 调用后端API匹配城市/区域
|
||||
// 这里先预留接口,后续实现
|
||||
postcodeMatching.value = true
|
||||
try {
|
||||
// const result = await matchPostcode(form.shippingCountry, newPostcode)
|
||||
// if (result && result.city) {
|
||||
// form.shippingCity = result.city
|
||||
// if (result.state) {
|
||||
// form.shippingState = result.state
|
||||
// }
|
||||
// }
|
||||
} catch (error) {
|
||||
console.error('邮编匹配失败:', error)
|
||||
} finally {
|
||||
postcodeMatching.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// 动态验证规则
|
||||
const getRules = () => {
|
||||
const baseRules = {
|
||||
customerName: [
|
||||
{ required: true, message: '请输入客户姓名', trigger: 'blur' }
|
||||
],
|
||||
customerPhone: [
|
||||
{ required: true, message: '请输入客户电话', trigger: 'blur' },
|
||||
{ pattern: /^[0-9+\-\s()]+$/, message: '请输入有效的电话号码', trigger: 'blur' }
|
||||
],
|
||||
customerEmail: [
|
||||
{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }
|
||||
],
|
||||
shippingName: [
|
||||
{ required: true, message: '请输入收货人姓名', trigger: 'blur' }
|
||||
],
|
||||
shippingPhone: [
|
||||
{ required: true, message: '请输入收货人电话', trigger: 'blur' },
|
||||
{ pattern: /^[0-9+\-\s()]+$/, message: '请输入有效的电话号码', trigger: 'blur' }
|
||||
],
|
||||
shippingCountry: [
|
||||
{ required: true, message: '请选择收货国家', trigger: 'change' }
|
||||
],
|
||||
shippingCity: [
|
||||
{ required: true, message: '请输入收货城市', trigger: 'blur' }
|
||||
],
|
||||
shippingStreet: [
|
||||
{ required: true, message: '请输入街道地址', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 根据国家配置添加必填字段验证
|
||||
if (currentCountryConfig.value) {
|
||||
const requiredFields = getRequiredFields(form.shippingCountry)
|
||||
|
||||
if (requiredFields.includes('shippingAddressLine1')) {
|
||||
baseRules.shippingAddressLine1 = [
|
||||
{ required: true, message: '请输入详细地址1', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
if (requiredFields.includes('shippingPostcode')) {
|
||||
baseRules.shippingPostcode = [
|
||||
{ required: true, message: '请输入邮编', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value && !validatePostcode(form.shippingCountry, value)) {
|
||||
callback(new Error(`邮编格式不正确,应为${currentCountryConfig.value.postcodeLength}位数字`))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if (requiredFields.includes('shippingBlockNumber')) {
|
||||
baseRules.shippingBlockNumber = [
|
||||
{ required: true, message: '请输入组屋号', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
if (requiredFields.includes('shippingUnitNumber')) {
|
||||
baseRules.shippingUnitNumber = [
|
||||
{ required: true, message: '请输入单元号', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
if (requiredFields.includes('shippingBarangay')) {
|
||||
baseRules.shippingBarangay = [
|
||||
{ required: true, message: '请输入Barangay(社区编号)', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
if (requiredFields.includes('shippingStateMalaysia')) {
|
||||
baseRules.shippingStateMalaysia = [
|
||||
{ required: true, message: '请输入州属', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
if (requiredFields.includes('shippingProvince')) {
|
||||
baseRules.shippingProvince = [
|
||||
{ required: true, message: '请输入省', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
if (requiredFields.includes('shippingDistrict')) {
|
||||
baseRules.shippingDistrict = [
|
||||
{ required: true, message: '请输入市/郡', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
if (requiredFields.includes('shippingWard')) {
|
||||
baseRules.shippingWard = [
|
||||
{ required: true, message: '请输入区/坊', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return baseRules
|
||||
}
|
||||
|
||||
const rules = computed(() => getRules())
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
router.back()
|
||||
@@ -252,8 +564,21 @@ const submitForm = async () => {
|
||||
shippingCountry: form.shippingCountry,
|
||||
shippingState: form.shippingState || null,
|
||||
shippingCity: form.shippingCity,
|
||||
shippingStreet: form.shippingStreet,
|
||||
shippingStreet: form.shippingStreet || form.shippingAddressLine1, // 兼容旧字段
|
||||
shippingPostcode: form.shippingPostcode || null,
|
||||
// 东南亚地址扩展字段
|
||||
shippingAddressLine1: form.shippingAddressLine1 || null,
|
||||
shippingAddressLine2: form.shippingAddressLine2 || null,
|
||||
shippingAdministrativeArea: form.shippingAdministrativeArea || null,
|
||||
shippingBlockNumber: form.shippingBlockNumber || null,
|
||||
shippingUnitNumber: form.shippingUnitNumber || null,
|
||||
shippingBarangay: form.shippingBarangay || null,
|
||||
shippingAddressThai: form.shippingAddressThai || null,
|
||||
shippingProvince: form.shippingProvince || null,
|
||||
shippingDistrict: form.shippingDistrict || null,
|
||||
shippingWard: form.shippingWard || null,
|
||||
shippingStateMalaysia: form.shippingStateMalaysia || null,
|
||||
shippingFloorUnit: form.shippingFloorUnit || null,
|
||||
remark: form.remark || null
|
||||
}
|
||||
|
||||
@@ -290,6 +615,17 @@ onMounted(() => {
|
||||
const data = JSON.parse(decodeURIComponent(route.query.data))
|
||||
if (data.product) {
|
||||
productInfo.value = data.product
|
||||
// 根据SKU的货币代码自动设置国家
|
||||
if (data.product.currency) {
|
||||
const countryCode = getCountryByCurrency(data.product.currency)
|
||||
if (countryCode) {
|
||||
form.shippingCountry = countryCode
|
||||
const config = getCountryConfig(countryCode)
|
||||
if (config) {
|
||||
form.shippingPhone = config.phoneCode + ' '
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析商品信息失败:', error)
|
||||
|
||||
@@ -87,7 +87,50 @@
|
||||
<el-descriptions-item label="收货人">{{ order.shippingName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="收货电话">{{ order.shippingPhone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="收货地址" :span="2">
|
||||
{{ formatShippingAddress(order) }}
|
||||
<div class="shipping-address-detail">
|
||||
<div v-if="order.shippingAddressLine1" class="address-line">
|
||||
<strong>详细地址1:</strong>{{ order.shippingAddressLine1 }}
|
||||
</div>
|
||||
<div v-if="order.shippingAddressLine2" class="address-line">
|
||||
<strong>详细地址2:</strong>{{ order.shippingAddressLine2 }}
|
||||
</div>
|
||||
<!-- 从JSON字段中读取特殊字段 -->
|
||||
<template v-if="order.shippingSpecialFields">
|
||||
<div v-if="order.shippingSpecialFields.blockNumber" class="address-line">
|
||||
<strong>组屋号:</strong>{{ order.shippingSpecialFields.blockNumber }}
|
||||
</div>
|
||||
<div v-if="order.shippingSpecialFields.unitNumber" class="address-line">
|
||||
<strong>单元号:</strong>{{ order.shippingSpecialFields.unitNumber }}
|
||||
</div>
|
||||
<div v-if="order.shippingSpecialFields.barangay" class="address-line">
|
||||
<strong>Barangay:</strong>{{ order.shippingSpecialFields.barangay }}
|
||||
</div>
|
||||
<div v-if="order.shippingSpecialFields.addressThai" class="address-line">
|
||||
<strong>泰文地址:</strong>{{ order.shippingSpecialFields.addressThai }}
|
||||
</div>
|
||||
<div v-if="order.shippingSpecialFields.province" class="address-line">
|
||||
<strong>省:</strong>{{ order.shippingSpecialFields.province }}
|
||||
</div>
|
||||
<div v-if="order.shippingSpecialFields.district" class="address-line">
|
||||
<strong>市/郡:</strong>{{ order.shippingSpecialFields.district }}
|
||||
</div>
|
||||
<div v-if="order.shippingSpecialFields.ward" class="address-line">
|
||||
<strong>区/坊:</strong>{{ order.shippingSpecialFields.ward }}
|
||||
</div>
|
||||
<div v-if="order.shippingSpecialFields.stateMalaysia" class="address-line">
|
||||
<strong>州属:</strong>{{ order.shippingSpecialFields.stateMalaysia }}
|
||||
</div>
|
||||
<div v-if="order.shippingSpecialFields.administrativeArea" class="address-line">
|
||||
<strong>行政区域:</strong>{{ order.shippingSpecialFields.administrativeArea }}
|
||||
</div>
|
||||
<div v-if="order.shippingSpecialFields.floorUnit" class="address-line">
|
||||
<strong>楼层/单元/代收点:</strong>{{ order.shippingSpecialFields.floorUnit }}
|
||||
</div>
|
||||
</template>
|
||||
<div class="address-line">
|
||||
{{ formatShippingAddress(order) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
@@ -199,15 +242,29 @@ const formatDateTime = (dateTime) => {
|
||||
return date.toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
// 格式化收货地址
|
||||
// 格式化收货地址(从小到大的地址格式:详细街道→城市→州/省→国家)
|
||||
const formatShippingAddress = (order) => {
|
||||
const parts = []
|
||||
if (order.shippingCountry) parts.push(order.shippingCountry)
|
||||
if (order.shippingState) parts.push(order.shippingState)
|
||||
// 详细地址1或街道地址
|
||||
if (order.shippingAddressLine1) {
|
||||
parts.push(order.shippingAddressLine1)
|
||||
} else if (order.shippingStreet) {
|
||||
parts.push(order.shippingStreet)
|
||||
}
|
||||
|
||||
if (order.shippingCity) parts.push(order.shippingCity)
|
||||
if (order.shippingStreet) parts.push(order.shippingStreet)
|
||||
|
||||
// 根据国家显示不同的州/省字段(从JSON字段中读取)
|
||||
if (order.shippingSpecialFields && order.shippingSpecialFields.stateMalaysia) {
|
||||
parts.push(order.shippingSpecialFields.stateMalaysia)
|
||||
} else if (order.shippingState) {
|
||||
parts.push(order.shippingState)
|
||||
}
|
||||
|
||||
if (order.shippingCountry) parts.push(order.shippingCountry)
|
||||
if (order.shippingPostcode) parts.push(`邮编:${order.shippingPostcode}`)
|
||||
return parts.join(' ')
|
||||
|
||||
return parts.length > 0 ? parts.join(',') : '地址信息不完整'
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
@@ -523,6 +580,19 @@ onMounted(() => {
|
||||
.rate-locked-info {
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.shipping-address-detail {
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.address-line {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.address-line strong {
|
||||
color: #606266;
|
||||
margin-right: 8px;
|
||||
color: #909399;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user