feat(i18n): 添加国际化配置和国家地址配置
- 新增 src/utils/countryConfig.js 文件,包含各国地址字段规则和邮编格式配置 - 新增 src/i18n/index.js 文件,实现基于货币代码的国际化翻译功能 - 新增 src/i18n/locales.js 文件,提供中英马泰菲多语言静态翻译文本 - 实现货币代码到国家代码映射及地址验证功能 - 添加订单创建页、商品详情页、订单确认页等多页面国际化支持 - 支持新加坡、马来西亚、菲律宾、泰国、越南等国家地址格式配置
This commit is contained in:
64
src/api/request.js
Normal file
64
src/api/request.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import config from '../config'
|
||||||
|
|
||||||
|
const request = axios.create({
|
||||||
|
baseURL: config.apiBaseUrl,
|
||||||
|
timeout: config.requestTimeout,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 请求拦截器
|
||||||
|
request.interceptors.request.use(
|
||||||
|
config => {
|
||||||
|
// 如果是文件上传,不设置 Content-Type,让浏览器自动设置(包含 boundary)
|
||||||
|
if (config.data instanceof FormData) {
|
||||||
|
delete config.headers['Content-Type']
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
request.interceptors.response.use(
|
||||||
|
response => {
|
||||||
|
const data = response.data
|
||||||
|
|
||||||
|
// 如果响应格式是统一的Result格式
|
||||||
|
if (data && typeof data === 'object' && 'code' in data) {
|
||||||
|
if (data.code === '0000') {
|
||||||
|
// 返回完整对象,让调用方自己处理
|
||||||
|
return data
|
||||||
|
} else {
|
||||||
|
ElMessage.error(data.message || '请求失败')
|
||||||
|
return Promise.reject(new Error(data.message || '请求失败'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error('请求错误:', error)
|
||||||
|
|
||||||
|
let message = '请求失败'
|
||||||
|
if (error.response) {
|
||||||
|
const data = error.response.data
|
||||||
|
message = data?.message || `请求失败: ${error.response.status}`
|
||||||
|
} else if (error.request) {
|
||||||
|
message = '网络错误,请检查网络连接'
|
||||||
|
} else {
|
||||||
|
message = error.message || '请求失败'
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.error(message)
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default request
|
||||||
|
|
||||||
137
src/i18n/index.js
Normal file
137
src/i18n/index.js
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
import { getLanguageByCurrency, getTranslationsByCurrency } from './locales'
|
||||||
|
|
||||||
|
// 默认中文文本(作为fallback)
|
||||||
|
const defaultMessages = {
|
||||||
|
// 商品详情页
|
||||||
|
product: {
|
||||||
|
selectCurrency: '选择货币',
|
||||||
|
selectSku: '选择商品规格(SKU)',
|
||||||
|
currentPrice: '现价',
|
||||||
|
quantity: '数量',
|
||||||
|
stock: '库存',
|
||||||
|
buyNow: '立即购买',
|
||||||
|
addToCart: '加入购物车',
|
||||||
|
productDetails: '商品详情',
|
||||||
|
specifications: '规格参数',
|
||||||
|
outOfStock: '缺货',
|
||||||
|
confirmPurchase: '确认购买',
|
||||||
|
cancel: '取消',
|
||||||
|
unitPrice: '单价',
|
||||||
|
total: '总计',
|
||||||
|
confirmPurchaseInfo: '确认购买信息',
|
||||||
|
sku: 'SKU',
|
||||||
|
productNotExist: '商品不存在或链接已过期',
|
||||||
|
linkExpired: '该商品链接可能已失效,请联系商家获取新的商品链接'
|
||||||
|
},
|
||||||
|
// 订单创建页
|
||||||
|
order: {
|
||||||
|
fillOrderInfo: '填写订单信息',
|
||||||
|
productInfo: '商品信息',
|
||||||
|
customerInfo: '客户信息',
|
||||||
|
shippingAddress: '收货地址',
|
||||||
|
customerName: '客户姓名',
|
||||||
|
customerPhone: '客户电话',
|
||||||
|
customerEmail: '客户邮箱',
|
||||||
|
shippingName: '收货人姓名',
|
||||||
|
shippingPhone: '收货人电话',
|
||||||
|
shippingCountry: '收货国家',
|
||||||
|
addressLine1: '详细地址1',
|
||||||
|
addressLine2: '详细地址2',
|
||||||
|
state: '州/省',
|
||||||
|
city: '城市',
|
||||||
|
postcode: '邮编',
|
||||||
|
remark: '备注',
|
||||||
|
submit: '提交订单',
|
||||||
|
back: '返回',
|
||||||
|
pleaseEnter: '请输入',
|
||||||
|
optional: '可选',
|
||||||
|
required: '必填',
|
||||||
|
addressFormat: '地址格式',
|
||||||
|
phoneCode: '国际区号',
|
||||||
|
mustMatchId: '需与证件一致,支持当地语言+英文'
|
||||||
|
},
|
||||||
|
// 订单确认页
|
||||||
|
confirm: {
|
||||||
|
orderInfo: '订单信息',
|
||||||
|
orderNo: '订单号',
|
||||||
|
orderStatus: '订单状态',
|
||||||
|
paymentStatus: '支付状态',
|
||||||
|
payNow: '立即支付',
|
||||||
|
viewOrder: '查看订单'
|
||||||
|
},
|
||||||
|
// 通用
|
||||||
|
common: {
|
||||||
|
loading: '加载中...',
|
||||||
|
submit: '提交',
|
||||||
|
cancel: '取消',
|
||||||
|
confirm: '确认',
|
||||||
|
save: '保存',
|
||||||
|
delete: '删除',
|
||||||
|
edit: '编辑',
|
||||||
|
search: '搜索',
|
||||||
|
reset: '重置',
|
||||||
|
operation: '操作',
|
||||||
|
success: '成功',
|
||||||
|
failed: '失败',
|
||||||
|
error: '错误',
|
||||||
|
warning: '警告',
|
||||||
|
info: '提示'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 i18n 实例
|
||||||
|
const i18n = createI18n({
|
||||||
|
legacy: false, // 使用 Composition API 模式
|
||||||
|
locale: 'zh', // 默认语言
|
||||||
|
fallbackLocale: 'zh', // 回退语言
|
||||||
|
messages: {
|
||||||
|
zh: defaultMessages
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 翻译缓存(避免重复请求)
|
||||||
|
const translationCache = new Map()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据货币代码加载翻译文本(使用静态翻译,不依赖后端API)
|
||||||
|
* @param {string} currency 货币代码
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function loadTranslationByCurrency(currency) {
|
||||||
|
if (!currency) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据货币代码获取语言代码
|
||||||
|
const language = getLanguageByCurrency(currency)
|
||||||
|
|
||||||
|
// 检查缓存
|
||||||
|
if (translationCache.has(language)) {
|
||||||
|
const cachedMessages = translationCache.get(language)
|
||||||
|
i18n.global.setLocaleMessage(language, cachedMessages)
|
||||||
|
i18n.global.locale.value = language
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从静态翻译文件获取翻译
|
||||||
|
const translatedMessages = getTranslationsByCurrency(currency)
|
||||||
|
|
||||||
|
// 缓存翻译结果
|
||||||
|
translationCache.set(language, translatedMessages)
|
||||||
|
|
||||||
|
// 设置翻译消息
|
||||||
|
i18n.global.setLocaleMessage(language, translatedMessages)
|
||||||
|
i18n.global.locale.value = language
|
||||||
|
|
||||||
|
console.log(`翻译加载成功,货币: ${currency}, 语言: ${language}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载翻译失败:', error)
|
||||||
|
// 翻译失败时使用默认中文
|
||||||
|
i18n.global.locale.value = 'zh'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default i18n
|
||||||
|
|
||||||
1080
src/i18n/locales.js
Normal file
1080
src/i18n/locales.js
Normal file
File diff suppressed because it is too large
Load Diff
166
src/utils/countryConfig.js
Normal file
166
src/utils/countryConfig.js
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
/**
|
||||||
|
* 国家地址配置
|
||||||
|
* 定义各国地址字段规则和邮编格式
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 货币代码到国家代码的映射
|
||||||
|
export const currencyToCountry = {
|
||||||
|
'USD': 'US',
|
||||||
|
'SGD': 'SG',
|
||||||
|
'MYR': 'MY',
|
||||||
|
'PHP': 'PH',
|
||||||
|
'THB': 'TH',
|
||||||
|
'VND': 'VN',
|
||||||
|
'CNY': 'CN',
|
||||||
|
'GBP': 'GB',
|
||||||
|
'EUR': 'DE'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 国家配置
|
||||||
|
export const countryConfigs = {
|
||||||
|
SG: {
|
||||||
|
code: 'SG',
|
||||||
|
name: '新加坡',
|
||||||
|
nameEn: 'Singapore',
|
||||||
|
phoneCode: '+65',
|
||||||
|
postcodeLength: 6,
|
||||||
|
postcodePattern: /^\d{6}$/,
|
||||||
|
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
||||||
|
'shippingAddressLine1', 'shippingBlockNumber', 'shippingUnitNumber', 'shippingPostcode'],
|
||||||
|
specialFields: ['shippingBlockNumber', 'shippingUnitNumber'],
|
||||||
|
fieldLabels: {
|
||||||
|
shippingBlockNumber: '组屋号 (Block Number)',
|
||||||
|
shippingUnitNumber: '单元号 (Unit Number)',
|
||||||
|
shippingAddressLine1: '详细地址1 (Address Line 1)',
|
||||||
|
shippingAddressLine2: '详细地址2 (Address Line 2)',
|
||||||
|
shippingCity: '城市 (City)',
|
||||||
|
shippingPostcode: '邮编 (Postcode)'
|
||||||
|
},
|
||||||
|
addressFormat: 'Blk 123 Jurong West St 41 #12-345, Singapore 640123'
|
||||||
|
},
|
||||||
|
MY: {
|
||||||
|
code: 'MY',
|
||||||
|
name: '马来西亚',
|
||||||
|
nameEn: 'Malaysia',
|
||||||
|
phoneCode: '+60',
|
||||||
|
postcodeLength: 5,
|
||||||
|
postcodePattern: /^\d{5}$/,
|
||||||
|
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
||||||
|
'shippingStateMalaysia', 'shippingAddressLine1', 'shippingPostcode'],
|
||||||
|
specialFields: ['shippingStateMalaysia'],
|
||||||
|
fieldLabels: {
|
||||||
|
shippingStateMalaysia: '州属 (State)',
|
||||||
|
shippingAddressLine1: '详细地址1 (Address Line 1)',
|
||||||
|
shippingAddressLine2: '详细地址2 (Address Line 2)',
|
||||||
|
shippingCity: '城市 (City)',
|
||||||
|
shippingPostcode: '邮编 (Postcode)'
|
||||||
|
},
|
||||||
|
addressFormat: '123 Jalan Abdullah, 05-01 Menara A, Kuala Lumpur, Selangor 50300'
|
||||||
|
},
|
||||||
|
PH: {
|
||||||
|
code: 'PH',
|
||||||
|
name: '菲律宾',
|
||||||
|
nameEn: 'Philippines',
|
||||||
|
phoneCode: '+63',
|
||||||
|
postcodeLength: 4,
|
||||||
|
postcodePattern: /^\d{4}$/,
|
||||||
|
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
||||||
|
'shippingState', 'shippingBarangay', 'shippingAddressLine1', 'shippingPostcode'],
|
||||||
|
specialFields: ['shippingBarangay'],
|
||||||
|
fieldLabels: {
|
||||||
|
shippingBarangay: 'Barangay(社区编号)',
|
||||||
|
shippingState: '省 (Province)',
|
||||||
|
shippingCity: '市 (City)',
|
||||||
|
shippingAddressLine1: '详细地址1 (Address Line 1)',
|
||||||
|
shippingAddressLine2: '详细地址2 (Address Line 2)',
|
||||||
|
shippingPostcode: '邮编 (Postcode)'
|
||||||
|
},
|
||||||
|
addressFormat: '123 Main St, Barangay 12, Manila, Metro Manila 1000'
|
||||||
|
},
|
||||||
|
TH: {
|
||||||
|
code: 'TH',
|
||||||
|
name: '泰国',
|
||||||
|
nameEn: 'Thailand',
|
||||||
|
phoneCode: '+66',
|
||||||
|
postcodeLength: 5,
|
||||||
|
postcodePattern: /^\d{5}$/,
|
||||||
|
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
||||||
|
'shippingState', 'shippingAddressLine1', 'shippingPostcode', 'shippingAddressThai'],
|
||||||
|
specialFields: ['shippingAddressThai', 'shippingAdministrativeArea'],
|
||||||
|
fieldLabels: {
|
||||||
|
shippingAddressThai: '泰文地址 (Thai Address)',
|
||||||
|
shippingAddressLine1: '英文地址 (English Address)',
|
||||||
|
shippingAddressLine2: '详细地址2 (Address Line 2)',
|
||||||
|
shippingState: '府 (Changwat)',
|
||||||
|
shippingCity: '县 (Amphoe)',
|
||||||
|
shippingAdministrativeArea: '区 (Tambon)',
|
||||||
|
shippingPostcode: '邮编 (Postcode)'
|
||||||
|
},
|
||||||
|
addressFormat: '123 Soi Sukhumvit 101, Khlong Toei, Bangkok 10110'
|
||||||
|
},
|
||||||
|
VN: {
|
||||||
|
code: 'VN',
|
||||||
|
name: '越南',
|
||||||
|
nameEn: 'Vietnam',
|
||||||
|
phoneCode: '+84',
|
||||||
|
postcodeLength: 5,
|
||||||
|
postcodePattern: /^\d{5}$/,
|
||||||
|
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingProvince',
|
||||||
|
'shippingDistrict', 'shippingWard', 'shippingAddressLine1', 'shippingPostcode'],
|
||||||
|
specialFields: ['shippingProvince', 'shippingDistrict', 'shippingWard'],
|
||||||
|
fieldLabels: {
|
||||||
|
shippingProvince: '省 (Tỉnh)',
|
||||||
|
shippingDistrict: '市/郡 (Thành phố/Huyện)',
|
||||||
|
shippingWard: '区/坊 (Quận/Phường)',
|
||||||
|
shippingAddressLine1: '详细地址1 (Địa chỉ chi tiết 1)',
|
||||||
|
shippingAddressLine2: '详细地址2 (Địa chỉ chi tiết 2)',
|
||||||
|
shippingPostcode: '邮编 (Postcode)'
|
||||||
|
},
|
||||||
|
addressFormat: '123 Đường Nguyễn Huệ, Phường Bến Nghé, Quận 1, Thành phố Hồ Chí Minh 70000'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据国家代码获取配置
|
||||||
|
*/
|
||||||
|
export function getCountryConfig(countryCode) {
|
||||||
|
return countryConfigs[countryCode] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据货币代码推断国家
|
||||||
|
*/
|
||||||
|
export function getCountryByCurrency(currency) {
|
||||||
|
return currencyToCountry[currency] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证邮编格式
|
||||||
|
*/
|
||||||
|
export function validatePostcode(countryCode, postcode) {
|
||||||
|
if (!postcode || !postcode.trim()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const config = getCountryConfig(countryCode)
|
||||||
|
if (!config) {
|
||||||
|
return true // 未知国家不验证
|
||||||
|
}
|
||||||
|
return config.postcodePattern.test(postcode.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取必填字段
|
||||||
|
*/
|
||||||
|
export function getRequiredFields(countryCode) {
|
||||||
|
const config = getCountryConfig(countryCode)
|
||||||
|
return config ? config.requiredFields : []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取特殊字段
|
||||||
|
*/
|
||||||
|
export function getSpecialFields(countryCode) {
|
||||||
|
const config = getCountryConfig(countryCode)
|
||||||
|
return config ? config.specialFields : []
|
||||||
|
}
|
||||||
|
|
||||||
32
src/utils/request.js
Normal file
32
src/utils/request.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 请求工具函数
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理API响应
|
||||||
|
*/
|
||||||
|
export function handleResponse(response) {
|
||||||
|
if (response.code === '0000') {
|
||||||
|
return response.data
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || '请求失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理错误
|
||||||
|
*/
|
||||||
|
export function handleError(error) {
|
||||||
|
if (error.response) {
|
||||||
|
// 服务器返回了错误响应
|
||||||
|
const { data } = error.response
|
||||||
|
return data?.message || `请求失败: ${error.response.status}`
|
||||||
|
} else if (error.request) {
|
||||||
|
// 请求已发出但没有收到响应
|
||||||
|
return '网络错误,请检查网络连接'
|
||||||
|
} else {
|
||||||
|
// 其他错误
|
||||||
|
return error.message || '请求失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
12
src/views/TestPage.vue
Normal file
12
src/views/TestPage.vue
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<div style="padding: 20px;">
|
||||||
|
<h1>测试页面</h1>
|
||||||
|
<p>如果你能看到这个页面,说明Vue应用正常运行。</p>
|
||||||
|
<el-button type="primary">测试按钮</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
console.log('TestPage组件已加载')
|
||||||
|
</script>
|
||||||
|
|
||||||
34
vite.config.js
Normal file
34
vite.config.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, 'src')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://127.0.0.1:8082', // 使用 127.0.0.1 而不是 localhost,避免 IPv6 问题
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
timeout: 30000,
|
||||||
|
configure: (proxy, options) => {
|
||||||
|
proxy.on('error', (err, req, res) => {
|
||||||
|
console.error('代理错误:', err.message)
|
||||||
|
console.error('请确保后端服务已启动在 http://127.0.0.1:8082')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
Reference in New Issue
Block a user