From 48156d72aa3a07156d186a0e8ea9277904ebec99 Mon Sep 17 00:00:00 2001 From: qiube <18969599531@163.com> Date: Thu, 25 Dec 2025 15:09:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E6=B7=BB=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=AE=A4=E8=AF=81=E7=B3=BB=E7=BB=9F=E5=92=8C=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E8=8F=9C=E5=8D=95=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现用户登录注册功能并添加路由守卫 - 将Element UI菜单替换为自定义导航菜单样式 - 添加多语言用户相关文本资源 - 重构订单确认页面货币转换逻辑 - 实现支付成功/取消页面返回商品详情功能 - 在商品管理页面添加用户信息入口 - 调整API请求添加ERP接口认证机制 - 优化移动端导航菜单适配样式 --- src/App.vue | 77 ++++++++-- src/api/request.js | 38 ++++- src/i18n/locales.js | 280 ++++++++++++++++++++++++++++++++++++ src/router/index.js | 49 ++++++- src/views/OrderConfirm.vue | 50 +++---- src/views/PayPalCancel.vue | 36 ++++- src/views/PayPalSuccess.vue | 36 ++++- src/views/ProductManage.vue | 75 +++++----- 8 files changed, 567 insertions(+), 74 deletions(-) diff --git a/src/App.vue b/src/App.vue index 0b3bbf7..14fca00 100644 --- a/src/App.vue +++ b/src/App.vue @@ -9,14 +9,22 @@

MT Pay 管理系统

- - 订单查询 - 商品管理 - +
@@ -56,6 +64,7 @@ const activeIndex = computed(() => route.path) color: white; line-height: 60px; padding: 0 20px; + overflow: visible; } .header-content { @@ -64,6 +73,58 @@ const activeIndex = computed(() => route.path) align-items: center; } +/* 导航菜单样式 - 直接显示,不隐藏 */ +.nav-menu { + display: flex; + gap: 0; + align-items: center; +} + +.nav-item { + display: inline-block; + padding: 0 20px; + line-height: 60px; + color: white; + text-decoration: none; + font-size: 14px; + cursor: pointer; + transition: background-color 0.3s; + white-space: nowrap; + border-bottom: 2px solid transparent; +} + +.nav-item:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.nav-item.active { + background-color: rgba(255, 255, 255, 0.2); + border-bottom: 2px solid white; +} + +/* 移动端适配 */ +@media (max-width: 768px) { + .header-content { + flex-wrap: wrap; + } + + .header-content h1 { + font-size: 16px; + margin-bottom: 0; + } + + .nav-menu { + width: 100%; + justify-content: center; + margin-top: 0; + } + + .nav-item { + padding: 0 15px; + font-size: 14px; + } +} + .header-content h1 { font-size: 20px; margin: 0; diff --git a/src/api/request.js b/src/api/request.js index d30018d..e2f3247 100644 --- a/src/api/request.js +++ b/src/api/request.js @@ -17,6 +17,17 @@ request.interceptors.request.use( if (config.data instanceof FormData) { delete config.headers['Content-Type'] } + + // 只有访问ERP接口时才添加Token + // 客户接口(商品、订单等)不需要Token + const isErpApi = config.url?.includes('/erp/') + if (isErpApi) { + const token = localStorage.getItem('token') + if (token) { + config.headers['Authorization'] = `Bearer ${token}` + } + } + return config }, error => { @@ -48,7 +59,32 @@ request.interceptors.response.use( let message = '请求失败' if (error.response) { const data = error.response.data - message = data?.message || `请求失败: ${error.response.status}` + const status = error.response.status + + // 处理401未授权错误(Token过期或无效) + // 只有访问ERP接口时才需要处理认证错误 + if (status === 401 || data?.code === '4002' || data?.code === '7004') { + // 清除Token和用户信息 + localStorage.removeItem('token') + localStorage.removeItem('userInfo') + + // 只有访问ERP相关接口或管理页面时才跳转到登录页 + const isErpApi = error.config?.url?.includes('/erp/') + const isManagePage = window.location.pathname.startsWith('/manage') + + if (isErpApi || isManagePage) { + if (window.location.pathname !== '/login' && window.location.pathname !== '/register') { + ElMessage.error('登录已过期,请重新登录') + setTimeout(() => { + window.location.href = '/login' + }, 1000) + } + } + + message = data?.message || '登录已过期,请重新登录' + } else { + message = data?.message || `请求失败: ${status}` + } } else if (error.request) { message = '网络错误,请检查网络连接' } else { diff --git a/src/i18n/locales.js b/src/i18n/locales.js index 76f9fc2..5cda75c 100644 --- a/src/i18n/locales.js +++ b/src/i18n/locales.js @@ -165,6 +165,46 @@ const zh = { currencySGD: '新加坡元', currencyHKD: '港币', currencyPHP: '菲律宾比索' + }, + // 用户相关 + user: { + login: '登录', + register: '注册', + logout: '退出登录', + username: '账号', + password: '密码', + nickName: '用户名称', + phone: '手机号', + email: '邮箱', + storeCode: '店铺号', + loginTitle: '用户登录', + registerTitle: '用户注册', + loginSuccess: '登录成功', + registerSuccess: '注册成功', + logoutSuccess: '退出登录成功', + usernameRequired: '请输入账号', + passwordRequired: '请输入密码', + storeCodeRequired: '请输入店铺号', + usernamePlaceholder: '请输入账号(3-50个字符)', + passwordPlaceholder: '请输入密码(6-20个字符)', + nickNamePlaceholder: '请输入用户名称(可选)', + phonePlaceholder: '请输入手机号(可选)', + emailPlaceholder: '请输入邮箱(可选)', + storeCodePlaceholder: '请输入店铺号', + usernameInvalid: '账号只能包含字母、数字和下划线', + passwordInvalid: '密码长度必须在6-20个字符之间', + phoneInvalid: '手机号格式不正确', + emailInvalid: '邮箱格式不正确', + noAccount: '还没有账号?', + hasAccount: '已有账号?', + goRegister: '立即注册', + goLogin: '立即登录', + rememberMe: '记住我', + forgotPassword: '忘记密码?', + userInfo: '用户信息', + welcome: '欢迎', + lastLoginTime: '最后登录时间', + lastLoginIp: '最后登录IP' } } @@ -327,6 +367,46 @@ const en = { currencySGD: 'Singapore Dollar', currencyHKD: 'Hong Kong Dollar', currencyPHP: 'Philippine Peso' + }, + // 用户相关 + user: { + login: 'Login', + register: 'Register', + logout: 'Logout', + username: 'Username', + password: 'Password', + nickName: 'Nick Name', + phone: 'Phone', + email: 'Email', + storeCode: 'Store Code', + loginTitle: 'User Login', + registerTitle: 'User Register', + loginSuccess: 'Login successful', + registerSuccess: 'Registration successful', + logoutSuccess: 'Logout successful', + usernameRequired: 'Please enter username', + passwordRequired: 'Please enter password', + storeCodeRequired: 'Please enter store code', + usernamePlaceholder: 'Please enter username (3-50 characters)', + passwordPlaceholder: 'Please enter password (6-20 characters)', + nickNamePlaceholder: 'Please enter nick name (optional)', + phonePlaceholder: 'Please enter phone number (optional)', + emailPlaceholder: 'Please enter email (optional)', + storeCodePlaceholder: 'Please enter store code', + usernameInvalid: 'Username can only contain letters, numbers and underscores', + passwordInvalid: 'Password length must be between 6-20 characters', + phoneInvalid: 'Invalid phone number format', + emailInvalid: 'Invalid email format', + noAccount: 'No account yet?', + hasAccount: 'Already have an account?', + goRegister: 'Register now', + goLogin: 'Login now', + rememberMe: 'Remember me', + forgotPassword: 'Forgot password?', + userInfo: 'User Information', + welcome: 'Welcome', + lastLoginTime: 'Last Login Time', + lastLoginIp: 'Last Login IP' } } @@ -486,6 +566,46 @@ const may = { currencySGD: 'Dolar Singapura', currencyHKD: 'Dolar Hong Kong', currencyPHP: 'Peso Filipina' + }, + // 用户相关 + user: { + login: 'Log Masuk', + register: 'Daftar', + logout: 'Log Keluar', + username: 'Nama Pengguna', + password: 'Kata Laluan', + nickName: 'Nama Panggilan', + phone: 'Telefon', + email: 'E-mel', + storeCode: 'Kod Kedai', + loginTitle: 'Log Masuk Pengguna', + registerTitle: 'Pendaftaran Pengguna', + loginSuccess: 'Log masuk berjaya', + registerSuccess: 'Pendaftaran berjaya', + logoutSuccess: 'Log keluar berjaya', + usernameRequired: 'Sila masukkan nama pengguna', + passwordRequired: 'Sila masukkan kata laluan', + storeCodeRequired: 'Sila masukkan kod kedai', + usernamePlaceholder: 'Sila masukkan nama pengguna (3-50 aksara)', + passwordPlaceholder: 'Sila masukkan kata laluan (6-20 aksara)', + nickNamePlaceholder: 'Sila masukkan nama panggilan (pilihan)', + phonePlaceholder: 'Sila masukkan nombor telefon (pilihan)', + emailPlaceholder: 'Sila masukkan e-mel (pilihan)', + storeCodePlaceholder: 'Sila masukkan kod kedai', + usernameInvalid: 'Nama pengguna hanya boleh mengandungi huruf, nombor dan garis bawah', + passwordInvalid: 'Panjang kata laluan mestilah antara 6-20 aksara', + phoneInvalid: 'Format nombor telefon tidak sah', + emailInvalid: 'Format e-mel tidak sah', + noAccount: 'Belum ada akaun?', + hasAccount: 'Sudah ada akaun?', + goRegister: 'Daftar sekarang', + goLogin: 'Log masuk sekarang', + rememberMe: 'Ingat saya', + forgotPassword: 'Lupa kata laluan?', + userInfo: 'Maklumat Pengguna', + welcome: 'Selamat datang', + lastLoginTime: 'Masa Log Masuk Terakhir', + lastLoginIp: 'IP Log Masuk Terakhir' } } @@ -592,6 +712,46 @@ const fil = { currencySGD: 'Singapore Dollar', currencyHKD: 'Hong Kong Dollar', currencyPHP: 'Philippine Peso' + }, + // 用户相关 + user: { + login: 'Mag-login', + register: 'Magrehistro', + logout: 'Mag-logout', + username: 'Username', + password: 'Password', + nickName: 'Palayaw', + phone: 'Telepono', + email: 'Email', + storeCode: 'Store Code', + loginTitle: 'User Login', + registerTitle: 'User Register', + loginSuccess: 'Matagumpay na pag-login', + registerSuccess: 'Matagumpay na pagrehistro', + logoutSuccess: 'Matagumpay na pag-logout', + usernameRequired: 'Mangyaring maglagay ng username', + passwordRequired: 'Mangyaring maglagay ng password', + storeCodeRequired: 'Mangyaring maglagay ng store code', + usernamePlaceholder: 'Mangyaring maglagay ng username (3-50 characters)', + passwordPlaceholder: 'Mangyaring maglagay ng password (6-20 characters)', + nickNamePlaceholder: 'Mangyaring maglagay ng palayaw (opsyonal)', + phonePlaceholder: 'Mangyaring maglagay ng numero ng telepono (opsyonal)', + emailPlaceholder: 'Mangyaring maglagay ng email (opsyonal)', + storeCodePlaceholder: 'Mangyaring maglagay ng store code', + usernameInvalid: 'Ang username ay maaari lamang maglaman ng mga titik, numero at underscore', + passwordInvalid: 'Ang haba ng password ay dapat nasa pagitan ng 6-20 characters', + phoneInvalid: 'Hindi wasto ang format ng numero ng telepono', + emailInvalid: 'Hindi wasto ang format ng email', + noAccount: 'Wala pang account?', + hasAccount: 'Mayroon nang account?', + goRegister: 'Magrehistro ngayon', + goLogin: 'Mag-login ngayon', + rememberMe: 'Tandaan ako', + forgotPassword: 'Nakalimutan ang password?', + userInfo: 'Impormasyon ng User', + welcome: 'Maligayang pagdating', + lastLoginTime: 'Huling Oras ng Login', + lastLoginIp: 'Huling IP ng Login' } } @@ -750,6 +910,46 @@ const th = { currencySGD: 'ดอลลาร์สิงคโปร์', currencyHKD: 'ดอลลาร์ฮ่องกง', currencyPHP: 'เปโซฟิลิปปินส์' + }, + // 用户相关 + user: { + login: 'เข้าสู่ระบบ', + register: 'ลงทะเบียน', + logout: 'ออกจากระบบ', + username: 'ชื่อผู้ใช้', + password: 'รหัสผ่าน', + nickName: 'ชื่อเล่น', + phone: 'เบอร์โทรศัพท์', + email: 'อีเมล', + storeCode: 'รหัสร้านค้า', + loginTitle: 'เข้าสู่ระบบผู้ใช้', + registerTitle: 'ลงทะเบียนผู้ใช้', + loginSuccess: 'เข้าสู่ระบบสำเร็จ', + registerSuccess: 'ลงทะเบียนสำเร็จ', + logoutSuccess: 'ออกจากระบบสำเร็จ', + usernameRequired: 'กรุณากรอกชื่อผู้ใช้', + passwordRequired: 'กรุณากรอกรหัสผ่าน', + storeCodeRequired: 'กรุณากรอกรหัสร้านค้า', + usernamePlaceholder: 'กรุณากรอกชื่อผู้ใช้ (3-50 ตัวอักษร)', + passwordPlaceholder: 'กรุณากรอกรหัสผ่าน (6-20 ตัวอักษร)', + nickNamePlaceholder: 'กรุณากรอกชื่อเล่น (ไม่บังคับ)', + phonePlaceholder: 'กรุณากรอกเบอร์โทรศัพท์ (ไม่บังคับ)', + emailPlaceholder: 'กรุณากรอกอีเมล (ไม่บังคับ)', + storeCodePlaceholder: 'กรุณากรอกรหัสร้านค้า', + usernameInvalid: 'ชื่อผู้ใช้สามารถมีได้เฉพาะตัวอักษร ตัวเลข และขีดล่าง', + passwordInvalid: 'ความยาวรหัสผ่านต้องอยู่ระหว่าง 6-20 ตัวอักษร', + phoneInvalid: 'รูปแบบเบอร์โทรศัพท์ไม่ถูกต้อง', + emailInvalid: 'รูปแบบอีเมลไม่ถูกต้อง', + noAccount: 'ยังไม่มีบัญชี?', + hasAccount: 'มีบัญชีแล้ว?', + goRegister: 'ลงทะเบียนตอนนี้', + goLogin: 'เข้าสู่ระบบตอนนี้', + rememberMe: 'จำฉันไว้', + forgotPassword: 'ลืมรหัสผ่าน?', + userInfo: 'ข้อมูลผู้ใช้', + welcome: 'ยินดีต้อนรับ', + lastLoginTime: 'เวลาเข้าสู่ระบบล่าสุด', + lastLoginIp: 'IP เข้าสู่ระบบล่าสุด' } } @@ -909,6 +1109,46 @@ const vie = { currencySGD: 'Đô La Singapore', currencyHKD: 'Đô La Hồng Kông', currencyPHP: 'Peso Philippines' + }, + // 用户相关 + user: { + login: 'Đăng nhập', + register: 'Đăng ký', + logout: 'Đăng xuất', + username: 'Tên đăng nhập', + password: 'Mật khẩu', + nickName: 'Tên hiển thị', + phone: 'Số điện thoại', + email: 'Email', + storeCode: 'Mã cửa hàng', + loginTitle: 'Đăng nhập người dùng', + registerTitle: 'Đăng ký người dùng', + loginSuccess: 'Đăng nhập thành công', + registerSuccess: 'Đăng ký thành công', + logoutSuccess: 'Đăng xuất thành công', + usernameRequired: 'Vui lòng nhập tên đăng nhập', + passwordRequired: 'Vui lòng nhập mật khẩu', + storeCodeRequired: 'Vui lòng nhập mã cửa hàng', + usernamePlaceholder: 'Vui lòng nhập tên đăng nhập (3-50 ký tự)', + passwordPlaceholder: 'Vui lòng nhập mật khẩu (6-20 ký tự)', + nickNamePlaceholder: 'Vui lòng nhập tên hiển thị (tùy chọn)', + phonePlaceholder: 'Vui lòng nhập số điện thoại (tùy chọn)', + emailPlaceholder: 'Vui lòng nhập email (tùy chọn)', + storeCodePlaceholder: 'Vui lòng nhập mã cửa hàng', + usernameInvalid: 'Tên đăng nhập chỉ có thể chứa chữ cái, số và dấu gạch dưới', + passwordInvalid: 'Độ dài mật khẩu phải từ 6-20 ký tự', + phoneInvalid: 'Định dạng số điện thoại không hợp lệ', + emailInvalid: 'Định dạng email không hợp lệ', + noAccount: 'Chưa có tài khoản?', + hasAccount: 'Đã có tài khoản?', + goRegister: 'Đăng ký ngay', + goLogin: 'Đăng nhập ngay', + rememberMe: 'Ghi nhớ đăng nhập', + forgotPassword: 'Quên mật khẩu?', + userInfo: 'Thông tin người dùng', + welcome: 'Chào mừng', + lastLoginTime: 'Thời gian đăng nhập cuối', + lastLoginIp: 'IP đăng nhập cuối' } } @@ -1015,6 +1255,46 @@ const id = { currencySGD: 'Dolar Singapura', currencyHKD: 'Dolar Hong Kong', currencyPHP: 'Peso Filipina' + }, + // 用户相关 + user: { + login: 'Masuk', + register: 'Daftar', + logout: 'Keluar', + username: 'Nama Pengguna', + password: 'Kata Sandi', + nickName: 'Nama Panggilan', + phone: 'Telepon', + email: 'Email', + storeCode: 'Kode Toko', + loginTitle: 'Masuk Pengguna', + registerTitle: 'Pendaftaran Pengguna', + loginSuccess: 'Berhasil masuk', + registerSuccess: 'Pendaftaran berhasil', + logoutSuccess: 'Berhasil keluar', + usernameRequired: 'Silakan masukkan nama pengguna', + passwordRequired: 'Silakan masukkan kata sandi', + storeCodeRequired: 'Silakan masukkan kode toko', + usernamePlaceholder: 'Silakan masukkan nama pengguna (3-50 karakter)', + passwordPlaceholder: 'Silakan masukkan kata sandi (6-20 karakter)', + nickNamePlaceholder: 'Silakan masukkan nama panggilan (opsional)', + phonePlaceholder: 'Silakan masukkan nomor telepon (opsional)', + emailPlaceholder: 'Silakan masukkan email (opsional)', + storeCodePlaceholder: 'Silakan masukkan kode toko', + usernameInvalid: 'Nama pengguna hanya dapat berisi huruf, angka dan garis bawah', + passwordInvalid: 'Panjang kata sandi harus antara 6-20 karakter', + phoneInvalid: 'Format nomor telepon tidak valid', + emailInvalid: 'Format email tidak valid', + noAccount: 'Belum punya akun?', + hasAccount: 'Sudah punya akun?', + goRegister: 'Daftar sekarang', + goLogin: 'Masuk sekarang', + rememberMe: 'Ingat saya', + forgotPassword: 'Lupa kata sandi?', + userInfo: 'Informasi Pengguna', + welcome: 'Selamat datang', + lastLoginTime: 'Waktu Masuk Terakhir', + lastLoginIp: 'IP Masuk Terakhir' } } diff --git a/src/router/index.js b/src/router/index.js index f87e09f..796d9dd 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -4,6 +4,9 @@ import Checkout from '../views/Checkout.vue' import PaymentResult from '../views/PaymentResult.vue' import OrderQuery from '../views/OrderQuery.vue' import ProductDetail from '../views/ProductDetail.vue' +import Login from '../views/Login.vue' +import Register from '../views/Register.vue' +import userStore from '../store/user' const routes = [ { @@ -11,6 +14,18 @@ const routes = [ name: 'Home', redirect: '/manage/product' }, + { + path: '/login', + name: 'Login', + component: Login, + meta: { requiresAuth: false } + }, + { + path: '/register', + name: 'Register', + component: Register, + meta: { requiresAuth: false } + }, { path: '/product/:id', name: 'ProductDetail', @@ -66,12 +81,20 @@ const routes = [ { path: '/manage/product', name: 'ProductManage', - component: () => import('../views/ProductManage.vue') + component: () => import('../views/ProductManage.vue'), + meta: { requiresAuth: true } }, { path: '/manage/product/create', name: 'ProductCreate', - component: () => import('../views/ProductCreate.vue') + component: () => import('../views/ProductCreate.vue'), + meta: { requiresAuth: true } + }, + { + path: '/manage/user/profile', + name: 'UserProfile', + component: () => import('../views/UserProfile.vue'), + meta: { requiresAuth: true } } ] @@ -80,9 +103,29 @@ const router = createRouter({ routes }) -// 添加路由守卫,用于调试 +// 路由守卫 router.beforeEach((to, from, next) => { console.log('路由导航:', from.path, '->', to.path) + + // 检查是否需要认证 + if (to.meta.requiresAuth) { + // 检查是否已登录 + if (!userStore.state.isLoggedIn) { + // 未登录,跳转到登录页,并保存目标路由 + next({ + path: '/login', + query: { redirect: to.fullPath } + }) + return + } + } + + // 如果已登录且访问登录/注册页,重定向到首页 + if (userStore.state.isLoggedIn && (to.path === '/login' || to.path === '/register')) { + next('/manage/product') + return + } + next() }) diff --git a/src/views/OrderConfirm.vue b/src/views/OrderConfirm.vue index f12771d..7c433cf 100644 --- a/src/views/OrderConfirm.vue +++ b/src/views/OrderConfirm.vue @@ -314,13 +314,17 @@ const loadOrder = async () => { await loadTranslationByCurrency(order.value.currency) } - // 如果订单未支付且需要货币转换,提前计算货币转换信息 + // 如果订单未支付且需要货币转换,提前计算货币转换信息(页面加载时就转换) if (order.value.paymentStatus === 'UNPAID' && order.value.currency) { - // 检查是否需要货币转换(PayPal支持的货币列表) - const supportedCurrencies = ['USD', 'EUR', 'GBP', 'AUD', 'CAD', 'JPY', 'CNY', 'HKD', 'SGD', 'NZD', + // PayPal支持的货币列表(注意:CNY和MYR不支持,需要转换为USD) + const supportedCurrencies = ['USD', 'EUR', 'GBP', 'AUD', 'CAD', 'JPY', 'HKD', 'SGD', 'NZD', 'CHF', 'SEK', 'NOK', 'DKK', 'PLN', 'MXN', 'BRL', 'INR', 'KRW', 'THB'] - const needsConversion = !supportedCurrencies.includes(order.value.currency) + // 检查是否需要货币转换: + // 1. CNY和MYR必须转换为USD(PayPal不支持) + // 2. 其他不支持的货币也需要转换 + const mustConvert = ['CNY', 'MYR'].includes(order.value.currency) + const needsConversion = mustConvert || !supportedCurrencies.includes(order.value.currency) // 如果订单还没有货币转换信息,或者需要更新,则计算 if (needsConversion && (!order.value.paymentCurrency || order.value.paymentCurrency === order.value.currency)) { @@ -340,11 +344,12 @@ const loadOrder = async () => { order.value.paymentAmount = conversion.paymentAmount order.value.exchangeRate = conversion.exchangeRate order.value.rateLockedAt = conversion.rateLockedAt - console.log('货币转换信息已计算并更新,支付状态:', order.value.paymentStatus) + console.log('货币转换信息已计算并更新(页面加载时),原始货币:', order.value.currency, + '支付货币:', order.value.paymentCurrency, '支付金额:', order.value.paymentAmount) } } catch (error) { - console.warn('计算货币转换信息失败:', error) - // 不显示错误,因为不影响订单显示 + console.error('计算货币转换信息失败:', error) + ElMessage.warning('计算货币转换信息失败,请稍后重试') } } } @@ -377,6 +382,9 @@ const handlePay = async () => { try { // 步骤2:构建PayPal订单创建请求 + // 注意:如果已经有货币转换信息(页面加载时已计算),使用转换后的货币和金额 + // 后端会根据currencyCode自动进行转换(CNY/MYR转USD),但这里使用原始货币和金额 + // 后端会返回转换后的信息,前端显示时使用order.value.paymentCurrency和paymentAmount const paypalOrderData = { intent: 'CAPTURE', // 立即捕获 referenceId: order.value.orderNo, // ERP订单号 @@ -407,25 +415,19 @@ const handlePay = async () => { const paypalOrder = responseData.paypalOrder || responseData // 兼容新旧格式 const currencyConversion = responseData.currencyConversion - // 如果有货币转换信息,更新订单显示 + // 如果有货币转换信息,更新订单显示(如果页面加载时已经计算过,这里只是确认) if (currencyConversion) { - order.value = { - ...order.value, - originalCurrency: currencyConversion.originalCurrency, - originalAmount: currencyConversion.originalAmount, - paymentCurrency: currencyConversion.paymentCurrency, - paymentAmount: currencyConversion.paymentAmount, - exchangeRate: currencyConversion.exchangeRate, - rateLockedAt: currencyConversion.rateLockedAt - } + // 更新订单显示信息(保留原有字段,只更新货币转换相关字段) + order.value.originalCurrency = currencyConversion.originalCurrency + order.value.originalAmount = currencyConversion.originalAmount + order.value.paymentCurrency = currencyConversion.paymentCurrency + order.value.paymentAmount = currencyConversion.paymentAmount + order.value.exchangeRate = currencyConversion.exchangeRate + order.value.rateLockedAt = currencyConversion.rateLockedAt - // 显示货币转换提示 - if (currencyConversion.conversionRequired) { - ElMessage.info({ - message: `${t('confirm.willPayIn', [getCurrencyName(currencyConversion.paymentCurrency)])},${t('confirm.actualCost')}:${currencyConversion.paymentCurrency} ${formatPrice(currencyConversion.paymentAmount)}`, - duration: 5000 - }) - } + // 如果页面加载时已经显示过转换信息,这里不需要再次提示 + // 只在首次转换时显示提示(但页面加载时已经计算过了,所以这里通常不需要显示) + console.log('PayPal订单创建成功,货币转换信息已确认,支付货币:', currencyConversion.paymentCurrency) } // 步骤3-4:获取approval_url并跳转到PayPal登录页 diff --git a/src/views/PayPalCancel.vue b/src/views/PayPalCancel.vue index 0657c8e..bb22169 100644 --- a/src/views/PayPalCancel.vue +++ b/src/views/PayPalCancel.vue @@ -34,6 +34,7 @@ import { ref, onMounted } from 'vue' import { useRouter, useRoute } from 'vue-router' import { ElMessage } from 'element-plus' import { getOrderByOrderNo } from '../api/order' +import { getProductUrl } from '../api/product' import { formatAmount } from '../utils/helpers' const router = useRouter() @@ -55,9 +56,38 @@ const continuePay = () => { } } -// 返回首页 -const goHome = () => { - router.push('/') +// 返回商品详情页 +const goHome = async () => { + if (order.value && order.value.productId) { + try { + // 获取商品链接 + const urlResponse = await getProductUrl(order.value.productId) + if (urlResponse.code === '0000' && urlResponse.data) { + const productUrl = urlResponse.data + // 从完整URL中提取链接码(URL格式:/product/link/xxxxx) + const linkCodeMatch = productUrl.match(/\/product\/link\/([^\/]+)/) + if (linkCodeMatch && linkCodeMatch[1]) { + // 使用链接码跳转到商品详情页 + router.push(`/product/link/${linkCodeMatch[1]}`) + return + } + // 如果URL格式是 /product/id,直接使用 + const idMatch = productUrl.match(/\/product\/(\d+)/) + if (idMatch && idMatch[1]) { + router.push(`/product/${idMatch[1]}`) + return + } + } + } catch (error) { + console.error('获取商品链接失败,使用productId跳转:', error) + } + // 如果获取链接失败或无法解析,直接使用productId跳转到商品详情页 + router.push(`/product/${order.value.productId}`) + } else { + // 如果没有订单信息或productId,仍然尝试跳转到商品详情页(使用默认方式) + // 这种情况理论上不应该发生,因为订单必须关联商品 + console.warn('订单缺少productId,无法跳转到商品详情页') + } } // 加载订单信息 diff --git a/src/views/PayPalSuccess.vue b/src/views/PayPalSuccess.vue index e4e690a..9982d63 100644 --- a/src/views/PayPalSuccess.vue +++ b/src/views/PayPalSuccess.vue @@ -70,6 +70,7 @@ import { ElMessage } from 'element-plus' import { Loading } from '@element-plus/icons-vue' import { getOrderByOrderNo } from '../api/order' import { getPayPalOrder, capturePayPalOrder } from '../api/paypal' +import { getProductUrl } from '../api/product' import { formatAmount } from '../utils/helpers' const router = useRouter() @@ -94,9 +95,38 @@ const viewOrder = () => { } } -// 返回首页 -const goHome = () => { - router.push('/') +// 返回商品详情页 +const goHome = async () => { + if (order.value && order.value.productId) { + try { + // 获取商品链接 + const urlResponse = await getProductUrl(order.value.productId) + if (urlResponse.code === '0000' && urlResponse.data) { + const productUrl = urlResponse.data + // 从完整URL中提取链接码(URL格式:/product/link/xxxxx) + const linkCodeMatch = productUrl.match(/\/product\/link\/([^\/]+)/) + if (linkCodeMatch && linkCodeMatch[1]) { + // 使用链接码跳转到商品详情页 + router.push(`/product/link/${linkCodeMatch[1]}`) + return + } + // 如果URL格式是 /product/id,直接使用 + const idMatch = productUrl.match(/\/product\/(\d+)/) + if (idMatch && idMatch[1]) { + router.push(`/product/${idMatch[1]}`) + return + } + } + } catch (error) { + console.error('获取商品链接失败,使用productId跳转:', error) + } + // 如果获取链接失败或无法解析,直接使用productId跳转到商品详情页 + router.push(`/product/${order.value.productId}`) + } else { + // 如果没有订单信息或productId,仍然尝试跳转到商品详情页(使用默认方式) + // 这种情况理论上不应该发生,因为订单必须关联商品 + console.warn('订单缺少productId,无法跳转到商品详情页') + } } // 处理PayPal支付回调(步骤7-11) diff --git a/src/views/ProductManage.vue b/src/views/ProductManage.vue index c7a8cd4..848fafb 100644 --- a/src/views/ProductManage.vue +++ b/src/views/ProductManage.vue @@ -4,10 +4,16 @@ @@ -124,7 +130,7 @@ import { ref, onMounted } from 'vue' import { useRouter } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' -import { Plus } from '@element-plus/icons-vue' +import { Plus, User } from '@element-plus/icons-vue' import { getProductList, getProductUrl } from '../api/product' import { formatAmount } from '../utils/helpers' @@ -137,6 +143,11 @@ const goToCreate = () => { router.push('/manage/product/create') } +// 跳转到用户信息页面 +const goToUserProfile = () => { + router.push('/manage/user/profile') +} + // 格式化价格 const formatPrice = (price) => { return formatAmount(price) @@ -186,24 +197,8 @@ const loadProductList = async () => { try { const response = await getProductList() if (response.code === '0000' && response.data) { - // 为每个商品获取链接URL - const productsWithUrl = await Promise.all( - response.data.map(async (product) => { - try { - const urlResponse = await getProductUrl(product.id) - if (urlResponse.code === '0000' && urlResponse.data && urlResponse.data.url) { - product.productUrl = urlResponse.data.url - } else { - product.productUrl = '' - } - } catch (error) { - console.error(`获取商品${product.id}链接失败:`, error) - product.productUrl = '' - } - return product - }) - ) - productList.value = productsWithUrl + // 后端已经返回了productUrl,直接使用 + productList.value = response.data } else { ElMessage.error(response.message || '获取商品列表失败') productList.value = [] @@ -228,22 +223,33 @@ const editProduct = (id) => { const copyProductUrl = async (id, url) => { try { let urlToCopy = url - // 如果没有URL,先获取 + // 如果没有URL,尝试获取(降级方案) if (!urlToCopy) { - const response = await getProductUrl(id) - if (response.code === '0000' && response.data && response.data.url) { - urlToCopy = response.data.url - // 更新列表中的URL - const product = productList.value.find(p => p.id === id) - if (product) { - product.productUrl = urlToCopy + try { + const response = await getProductUrl(id) + if (response.code === '0000' && response.data && response.data.url) { + urlToCopy = response.data.url + // 更新列表中的URL + const product = productList.value.find(p => p.id === id) + if (product) { + product.productUrl = urlToCopy + } + } else { + ElMessage.error('获取商品链接失败') + return } - } else { + } catch (error) { + console.error('获取商品链接失败:', error) ElMessage.error('获取商品链接失败') return } } + if (!urlToCopy) { + ElMessage.error('商品链接为空') + return + } + // 复制到剪贴板 await navigator.clipboard.writeText(urlToCopy) ElMessage.success('商品链接已复制到剪贴板') @@ -296,6 +302,11 @@ onMounted(() => { font-weight: bold; } +.header-actions { + display: flex; + gap: 10px; +} + /* 商品名称单元格 */ .product-name-cell { display: flex;