Commit d49e03f5 authored by 罗超's avatar 罗超

Merge branch 'master' of http://gitlab.oytour.com/youjie/boyuecend

# Conflicts:
#	src/stores/systemConfig.ts
#	src/views/personalCenter/accountPage/account.vue
parents 0cb5628f 6dc44cb4
......@@ -57,6 +57,7 @@ onMounted(async () => {
<style lang="scss">
body {
--arcoblue-11: 240, 241, 235;//#F0F1EB
--arcoblue-10: 245, 246, 240;//#F5F6F0
--arcoblue-9: 255, 151, 7;//#FF9707
--arcoblue-8: 38, 54, 40;//#263628
......@@ -96,6 +97,7 @@ body {
}
:root {
--customPrimary-11: #F0F1EB;
--customPrimary-10: #F5F6F0;
--customPrimary-9: #ff9707;
--customPrimary-8: #263628;
......@@ -116,6 +118,7 @@ body {
--customColor-text-5: #4A664D;
--customColor-text-4: #8EAD8E;
}
.customPrimary-11{color: var(--customPrimary-11);}
.customPrimary-10{color: var(--customPrimary-10);}
.customPrimary-9{color: var(--customPrimary-9);}
.customPrimary-8{color: var(--customPrimary-8);}
......@@ -127,6 +130,7 @@ body {
.customPrimary-2{color: var(--customPrimary-2);}
.customPrimary-1{color: var(--customPrimary-1);}
.customPrimary-bg-11{background-color: var(--customPrimary-11);}
.customPrimary-bg-10{background-color: var(--customPrimary-10);}
.customPrimary-bg-9{background-color: var(--customPrimary-9);}
.customPrimary-bg-8{background-color: var(--customPrimary-8);}
......
import { enCountries } from './countries'
import customFieldsEn from './fields/custom-fields-en.json'
import pageFieldsEn from './fields/page-fields-en.json'
import pageTitleEn from './page/page-title-en.json'
export default {
// 国家名称(基于ISO2代码)
......@@ -97,7 +97,11 @@ export default {
loginFailed: "Login failed",
resetSuccess: "Password reset successful",
resetFailed: "Password reset failed",
emailRequiredReset: "Please enter email"
emailRequiredReset: "Please enter email",
wechatLoginFailed: 'WeChat Login Failed',
wechatBindFailed: 'WeChat Bind Failed',
lineLoginFailed: 'Line Login Failed',
lineBindFailed: 'Line Bind Failed',
},
common: {
language: 'Language',
......@@ -191,6 +195,12 @@ export default {
profile: 'My Profile',
settings: 'Settings',
logout: 'Sign Out',
accommodation: 'Accommodation',
transportation: 'Transportation',
tripExperience: 'Trip Experience',
sightseeingTicket: 'Sightseeing Ticket',
travelService: 'Travel Service',
food: 'Food'
},
// Email Validation
emailValidation: {
......@@ -200,6 +210,116 @@ export default {
invalidFormat: 'Invalid email format',
checkFailed: 'Email validation failed, please try again later',
},
personal: {
completeProfile: 'Complete Profile',
orderCenter: 'Order Center',
profile: 'Personal Center',
promotionManagement: 'Promotion Management',
menu:{
myOrder: 'My Orders',
systemMessage: 'System Messages',
myCollection: 'My Collections',
coupon: 'Coupons',
accountCenter: 'Account Center',
commonPassengerInfo: 'Common Passenger Info',
distributionCenter: 'Distribution Center',
},
bindingEmail: 'Bind Email',
unbound: 'Unbound',
distributionCenter: 'My Distribution',
invitationCode: 'My Invitation Code',
orderStatus: {
ALL: 'All Orders',
UN_PAY: 'Unpaid',
PAYED: 'Pending Travel',
FINISH: 'Pending Review',
CANCEL: 'Canceled',
},
noOrder: 'No Orders Yet',
orderNumber: 'Order Number',
reserveDate: 'Reservation Date',
checkInDate: 'Check-in Date',
guestName: 'Guest',
roomType: 'Room Type',
to: 'to',
systemMessage: 'System Messages',
orderType: 'All Types',
management: 'Management',
editEmail: 'Edit Email',
binEmail: 'Bind Email',
bindEmailFailed: 'Bind Email Failed',
save: 'Save',
bindGoogleAccount: 'Bind Google Account',
bindWechatAccount: 'Bind WeChat Account',
bindLineAccount: 'Bind Line Account',
bindGoogleFailed: 'Bind Google Failed',
bindWechatSuccess: 'Bind WeChat Success',
bindGoogleSuccess: 'Bind Google Success',
changeBind: 'Change Bind',
unBind: 'Unbind',
securityLevel: 'Security Level',
low: 'Low',
medium: 'Medium',
high: 'High',
passwordSecurityTip: ' A highly secure password can make your account safer. We recommend changing your password regularly and setting a password that includes numbers and letters, with a length of over 8 characters.',
bindPhoneTip: 'Bind phone. ',
changeBindData: 'Change',
bindEmailTip: 'Email is used for receiving verification codes and account login.',
bindWechatTip: 'Quick login via WeChat account without entering a password.',
bindGoogleTip: 'Quick login via Google account without entering a password.',
bindLineTip: 'Quick login via Line account without entering a password.',
notYetBin: 'Not Bound Yet',
goBind: 'Go Bind',
confirmChange: 'Confirm Change',
return: 'Return',
oldPassword: 'Old Password',
placeholderNewPassword: 'Please enter new password',
phoneCaptcha: 'Phone Captcha',
wechat: 'WeChat',
accountPassword: 'Account Password',
unBindSuccess: 'Unbind Success',
unBindError: 'Unbind Error',
nextStepSetPassword: 'Next Step Set Password',
returnStep: 'Return to Previous Step',
basicInfo: 'Basic Info',
accountInfor: 'Account Information',
commonPassenger: 'Common Passengers',
firstName: 'First Name',
lastName: 'Last Name',
gender: 'Gender',
placeholderLastName: 'Please enter last name',
placeholderFirstName: 'Please enter first name',
placeholderGender: 'Please select gender',
birthday: 'Birthday',
placeholder: 'Please select',
changePhoto: 'Change Photo',
photo: 'Photo',
photoTip: '*Supports jpg, gif, png format images, and file size less than 2M.',
cancel: 'Cancel',
addPassenger: 'Add Passenger',
editPassenger: 'Edit Passenger',
deleteSuccess: 'Delete Success',
idCard: 'ID Card Number',
placeholderIdCard: 'Please enter ID card number',
couponStatus: {
UNUSED: 'Unused',
USED: 'Used',
EXPIRED: 'Expired',
},
postAddress: 'Mailing Address',
addPostAddress: 'Add Mailing Address',
editPostAddress: 'Edit Mailing Address',
defaultAddress: 'Default Address',
recipient: 'Recipient',
placeholderRecipient: 'Please enter recipient',
postalCode: 'Postal Code',
placeholderPostalCode: 'Please enter postal code',
address: 'Detailed Address',
placeholderAddress: 'Please enter detailed address',
city: 'City',
placeholderCity: 'Please enter city',
default: 'Default',
},
// HTTP error status codes
httpError: {
400: 'Bad request',
......@@ -220,6 +340,6 @@ export default {
// Custom form fields
...customFieldsEn,
// Page form fields
...pageFieldsEn,
// Page title
...pageTitleEn,
}
\ No newline at end of file
{
"page": {
"login": "Login",
"register": "Register",
"forgotPassword": "Forgot Password",
"notPermission": "Access Denied",
"products": "products"
}
}
\ No newline at end of file
{
"page": {
"login": "Đăng nhập",
"register": "Đăng ký",
"forgotPassword": "Quên mật khẩu",
"notPermission": "Truy cập bị từ chối",
"products": "sản phẩm"
}
}
\ No newline at end of file
{
"page": {
"login": "登入",
"register": "註冊",
"forgotPassword": "忘記密碼",
"notPermission": "無權限訪問",
"products": "產品"
}
}
\ No newline at end of file
{
"page": {
"login": "Login",
"register": "Register",
"forgotPassword": "Forgot Password",
"notPermission": "Access Denied",
"products": "products",
"home": "Home",
"profile": "Personal Center",
"myOrder": "My Orders",
"systemMessage": "System Messages",
"myCollection": "My Collections",
"coupon": "Coupons",
"accountCenter": "Account Center",
"commonPassengerInfo": "Common Passenger Info",
"distributionCenter": "Distribution Center",
"resetPassword": "Reset Password",
"editEmail": "Bind/Edit Email"
}
}
\ No newline at end of file
{
"page": {
"login": "Đăng nhập",
"register": "Đăng ký",
"forgotPassword": "Quên mật khẩu",
"notPermission": "Truy cập bị từ chối",
"products": "sản phẩm",
"home": "Trang chủ",
"profile": "Trung tâm cá nhân",
"myOrder": "Đơn hàng của tôi",
"systemMessage": "Thông báo hệ thống",
"myCollection": "Bộ sưu tập của tôi",
"coupon": "Phiếu giảm giá",
"accountCenter": "Trung tâm tài khoản",
"commonPassengerInfo": "Thông tin hành khách thường dùng",
"distributionCenter": "Trung tâm phân phối",
"resetPassword": "Đặt lại mật khẩu",
"editEmail": "Liên kết/Sửa đổi email"
}
}
\ No newline at end of file
......@@ -15,6 +15,10 @@
"commonPassengerInfo": "常用旅客信息",
"distributionCenter": "分销中心",
"resetPassword": "重置密码",
"editEmail": "绑定/修改邮箱"
"editEmail": "绑定/修改邮箱",
"basicInfor": "基础资料",
"account": "账户信息",
"passengerList": "常用旅客",
"mailingAddressList": "邮寄地址"
}
}
\ No newline at end of file
{
"page": {
"login": "登入",
"register": "註冊",
"forgotPassword": "忘記密碼",
"notPermission": "無權限訪問",
"products": "產品",
"home": "首頁",
"profile": "個人中心",
"myOrder": "我的訂單",
"systemMessage": "系統訊息",
"myCollection": "我的收藏",
"coupon": "優惠券",
"accountCenter": "帳戶中心",
"commonPassengerInfo": "常用旅客資訊",
"distributionCenter": "分銷中心",
"resetPassword": "重設密碼",
"editEmail": "綁定/修改郵箱"
}
}
\ No newline at end of file
import { viCountries } from './countries'
import customFieldsVi from './fields/custom-fields-vi.json'
import pageFieldsVi from './fields/page-fields-vi.json'
import pageTitleVi from './page/page-title-vi.json'
export default {
// 国家名称(基于ISO2代码)
......@@ -97,7 +97,11 @@ export default {
loginFailed: "Đăng nhập thất bại",
resetSuccess: "Đặt lại mật khẩu thành công",
resetFailed: "Đặt lại mật khẩu thất bại",
emailRequiredReset: "Vui lòng nhập email"
emailRequiredReset: "Vui lòng nhập email",
wechatLoginFailed: 'Đăng nhập WeChat thất bại',
wechatBindFailed: 'Liên kết WeChat thất bại',
lineLoginFailed: 'Đăng nhập Line thất bại',
lineBindFailed: 'Liên kết Line thất bại',
},
common: {
language: 'Ngôn ngữ',
......@@ -191,6 +195,12 @@ export default {
profile: 'Hồ sơ cá nhân',
settings: 'Cài đặt',
logout: 'Đăng xuất',
accommodation: 'lưu trú',
transportation: 'giao thông',
tripExperience: 'Lịch trình & Trải nghiệm',
sightseeingTicket: 'Vé tham quan điểm du lịch',
travelService: 'Dịch vụ du lịch',
food: 'món ngon'
},
// Xác thực email
emailValidation: {
......@@ -200,6 +210,116 @@ export default {
invalidFormat: 'Định dạng email không chính xác',
checkFailed: 'Xác thực email thất bại, vui lòng thử lại sau',
},
personal: {
completeProfile: 'Hoàn thiện thông tin',
orderCenter: 'Trung tâm đơn hàng',
profile: 'Trung tâm cá nhân',
promotionManagement: 'Quản lý khuyến mãi',
menu:{
myOrder: 'Đơn hàng của tôi',
systemMessage: 'Thông báo hệ thống',
myCollection: 'Bộ sưu tập của tôi',
coupon: 'Phiếu giảm giá',
accountCenter: 'Trung tâm tài khoản',
commonPassengerInfo: 'Thông tin hành khách thường dùng',
distributionCenter: 'Trung tâm phân phối',
},
bindingEmail: 'Liên kết email',
unbound: 'Chưa liên kết',
distributionCenter: 'Phân phối của tôi',
invitationCode: 'Mã mời của tôi',
orderStatus: {
ALL: 'Tất cả đơn hàng',
UN_PAY: 'Chờ thanh toán',
PAYED: 'Chờ đi lại',
FINISH: 'Chờ đánh giá',
CANCEL: 'Đã hủy',
},
noOrder: 'Chưa có đơn hàng',
orderNumber: 'Số đơn hàng',
reserveDate: 'Ngày đặt trước',
checkInDate: 'Ngày nhận phòng',
guestName: 'Khách',
roomType: 'Loại phòng',
to: 'đến',
systemMessage: 'Thông báo hệ thống',
orderType: 'Tất cả loại',
management: 'Quản lý',
editEmail: 'Chỉnh sửa email',
binEmail: 'Liên kết email',
bindEmailFailed: 'Liên kết email thất bại',
save: 'Lưu',
bindGoogleAccount: 'Liên kết tài khoản Google',
bindWechatAccount: 'Liên kết tài khoản WeChat',
bindLineAccount: 'Liên kết tài khoản Line',
bindGoogleFailed: 'Liên kết Google thất bại',
bindWechatSuccess: 'Liên kết WeChat thành công',
bindGoogleSuccess: 'Liên kết Google thành công',
changeBind: 'Đổi liên kết',
unBind: 'Hủy liên kết',
securityLevel: 'Mức độ bảo mật',
low: 'Thấp',
medium: 'Trung bình',
high: 'Cao',
passwordSecurityTip: ' Mật khẩu có độ bảo mật cao có thể làm cho tài khoản an toàn hơn. Chúng tôi đề nghị bạn thay đổi mật khẩu định kỳ và đặt một mật khẩu bao gồm số và chữ cái, với độ dài trên 8 ký tự.',
bindPhoneTip: 'Liên kết điện thoại. ',
changeBindData: 'Thay đổi',
bindEmailTip: 'Email dùng để nhận mã xác thực, đăng nhập tài khoản.',
bindWechatTip: 'Đăng nhập nhanh qua tài khoản WeChat, không cần nhập mật khẩu.',
bindGoogleTip: 'Đăng nhập nhanh qua tài khoản Google, không cần nhập mật khẩu.',
bindLineTip: 'Đăng nhập nhanh qua tài khoản Line, không cần nhập mật khẩu.',
notYetBin: 'Chưa liên kết',
goBind: 'Đi liên kết',
confirmChange: 'Xác nhận sửa đổi',
return: 'Quay lại',
oldPassword: 'Mật khẩu cũ',
placeholderNewPassword: 'Vui lòng nhập mật khẩu mới',
phoneCaptcha: 'Mã xác thực điện thoại',
wechat: 'WeChat',
accountPassword: 'Mật khẩu tài khoản',
unBindSuccess: 'Hủy liên kết thành công',
unBindError: 'Hủy liên kết thất bại',
nextStepSetPassword: 'Bước tiếp theo đặt mật khẩu',
returnStep: 'Quay lại bước trước',
basicInfo: 'Thông tin cơ bản',
accountInfor: 'Thông tin tài khoản',
commonPassenger: 'Hành khách thường dùng',
firstName: 'Tên',
lastName: 'Họ',
gender: 'Giới tính',
placeholderLastName: 'Vui lòng nhập họ',
placeholderFirstName: 'Vui lòng nhập tên',
placeholderGender: 'Vui lòng chọn giới tính',
birthday: 'Ngày sinh',
placeholder: 'Vui lòng chọn',
changePhoto: 'Sửa đổi ảnh đại diện',
photo: 'Ảnh đại diện',
photoTip: '*Hỗ trợ định dạng ảnh jpg, gif, png, và tệp nhỏ hơn 2M.',
cancel: 'Hủy',
addPassenger: 'Thêm hành khách',
editPassenger: 'Sửa hành khách',
deleteSuccess: 'Xóa thành công',
idCard: 'Số CMND',
placeholderIdCard: 'Vui lòng nhập số CMND',
couponStatus: {
UNUSED: 'Chưa sử dụng',
USED: 'Đã sử dụng',
EXPIRED: 'Đã hết hạn',
},
postAddress: 'Địa chỉ gửi thư',
addPostAddress: 'Thêm địa chỉ gửi thư',
editPostAddress: 'Sửa địa chỉ gửi thư',
defaultAddress: 'Địa chỉ mặc định',
recipient: 'Người nhận',
placeholderRecipient: 'Vui lòng nhập người nhận',
postalCode: 'Mã bưu chính',
placeholderPostalCode: 'Vui lòng nhập mã bưu chính',
address: 'Địa chỉ chi tiết',
placeholderAddress: 'Vui lòng nhập địa chỉ chi tiết',
city: 'Thành phố',
placeholderCity: 'Vui lòng nhập thành phố',
default: 'Mặc định',
},
// Mã lỗi HTTP
httpError: {
400: 'Yêu cầu không hợp lệ',
......@@ -221,5 +341,5 @@ export default {
// Trường biểu mẫu tùy chỉnh
...customFieldsVi,
// Các trang
...pageFieldsVi,
...pageTitleVi,
}
\ No newline at end of file
import { zhCNCountries } from './countries'
import customFieldsZhCN from './fields/custom-fields-zh-CN.json'
import pageFieldsZhCN from './fields/page-fields-zh-CN.json'
import pageTitleZhCN from './page/page-title-zh-CN.json'
export default {
// 国家名称(基于ISO2代码)
......@@ -99,6 +99,9 @@ export default {
resetFailed: '密码重置失败',
emailRequiredReset: '请输入邮箱',
wechatLoginFailed: '微信登录失败',
wechatBindFailed: '微信绑定失败',
lineLoginFailed: 'Line登录失败',
lineBindFailed: 'Line绑定失败',
},
common: {
language: '语言',
......@@ -316,6 +319,15 @@ export default {
city: '城市',
placeholderCity: '请输入城市',
default: '默认',
validPeriod: '有效期',
useCoupon: '去使用',
discount: '折',
noCoupon: '暂无优惠券',
noCollection: '暂无收藏',
photoSize: '图片大小不能超过{size}MB',
uploadComplete: '图片上传完成',
uploadFailed: '上传失败',
updateSuccess: '保存成功',
},
// HTTP 错误状态码
httpError: {
......@@ -337,6 +349,6 @@ export default {
// 自定义表单字段
...customFieldsZhCN,
// 页面表单字段
...pageFieldsZhCN,
// 页面标题
...pageTitleZhCN,
}
\ No newline at end of file
import { zhTWCountries } from './countries'
import customFieldsZhTW from './fields/custom-fields-zh-TW.json'
import pageFieldsZhTW from './fields/page-fields-zh-TW.json'
import pageTitleZhTW from './page/page-title-zh-TW.json'
export default {
// 国家名称(基于ISO2代码)
......@@ -97,7 +97,11 @@ export default {
loginFailed: "登錄失敗",
resetSuccess: "密碼重置成功",
resetFailed: "密碼重置失敗",
emailRequiredReset: "請輸入郵箱"
emailRequiredReset: "請輸入郵箱",
wechatLoginFailed: '微信登入失敗',
wechatBindFailed: '微信綁定失敗',
lineLoginFailed: 'Line登入失敗',
lineBindFailed: 'Line綁定失敗',
},
common: {
language: '語言',
......@@ -191,7 +195,14 @@ export default {
profile: '個人檔案',
settings: '設定',
logout: '登出',
accommodation: '住宿',
transportation: '交通',
tripExperience: '行程&體驗',
sightseeingTicket: '景點門票',
travelService: '旅行服務',
food: '美食'
},
// 邮箱验证
emailValidation: {
checking: '正在檢查郵箱...',
available: '郵箱可用',
......@@ -199,6 +210,116 @@ export default {
invalidFormat: '郵箱格式不正確',
checkFailed: '郵箱驗證失敗,請稍後重試',
},
personal: {
completeProfile: '完善資料',
orderCenter: '訂單中心',
profile: '個人中心',
promotionManagement: '推廣管理',
menu:{
myOrder: '我的訂單',
systemMessage: '系統訊息',
myCollection: '我的收藏',
coupon: '優惠券',
accountCenter: '帳戶中心',
commonPassengerInfo: '常用旅客資訊',
distributionCenter: '分銷中心',
},
bindingEmail: '綁定郵箱',
unbound: '未綁定',
distributionCenter: '我的分銷',
invitationCode: '我的邀請碼',
orderStatus: {
ALL: '全部訂單',
UN_PAY: '待付款',
PAYED: '待出行',
FINISH: '待評價',
CANCEL: '已取消',
},
noOrder: '暫無訂單',
orderNumber: '訂單號',
reserveDate: '預定日期',
checkInDate: '入住日期',
guestName: '客人',
roomType: '房型',
to: '至',
systemMessage: '系統訊息',
orderType: '所有類型',
management: '管理',
editEmail: '編輯郵箱',
binEmail: '綁定郵箱',
bindEmailFailed: '綁定郵箱失敗',
save: '保存',
bindGoogleAccount: '綁定Google',
bindWechatAccount: '綁定微信',
bindLineAccount: '綁定Line',
bindGoogleFailed: '綁定Google失敗',
bindWechatSuccess: '綁定微信成功',
bindGoogleSuccess: '綁定Google成功',
changeBind: '換綁',
unBind: '解綁',
securityLevel: '安全等級',
low: '低',
medium: '中',
high: '高',
passwordSecurityTip: ' 安全性高的密碼可以使帳號更安全。建議您定期更換密碼,且設置一個包含數字和字母,並且長度超過8位以上的密碼。',
bindPhoneTip: '綁定手機。 ',
changeBindData: '更換',
bindEmailTip: '郵箱用於驗證碼接收,帳戶登入。',
bindWechatTip: '通過微信帳戶快速登入,無需輸入密碼。',
bindGoogleTip: '通過谷歌帳戶快速登入,無需輸入密碼。',
bindLineTip: '通過Line帳戶快速登入,無需輸入密碼。',
notYetBin: '暫未綁定',
goBind: '去綁定',
confirmChange: '確認修改',
return: '返回',
oldPassword: '舊密碼',
placeholderNewPassword: '請輸入新密碼',
phoneCaptcha: '手機驗證碼',
wechat: '微信',
accountPassword: '帳號密碼',
unBindSuccess: '解綁成功',
unBindError: '解綁失敗',
nextStepSetPassword: '下一步設置密碼',
returnStep: '返回上一步',
basicInfo: '基礎資料',
accountInfor: '帳戶資訊',
commonPassenger: '常用旅客',
firstName: '名',
lastName: '姓',
gender: '性別',
placeholderLastName: '請輸入姓',
placeholderFirstName: '請輸入名',
placeholderGender: '請選擇性別',
birthday: '出生日期',
placeholder: '請選擇',
changePhoto: '修改頭像',
photo: '頭像',
photoTip: '*支援jpg、gif、png格式圖片,且檔案小於2M。',
cancel: '取消',
addPassenger: '新增旅客',
editPassenger: '修改旅客',
deleteSuccess: '刪除成功',
idCard: '身份證號',
placeholderIdCard: '請輸入身份證號',
couponStatus: {
UNUSED: '未使用',
USED: '已使用',
EXPIRED: '已過期',
},
postAddress: '郵寄地址',
addPostAddress: '新增郵寄地址',
editPostAddress: '修改郵寄地址',
defaultAddress: '默認地址',
recipient: '收件人',
placeholderRecipient: '請輸入收件人',
postalCode: '郵遞區號',
placeholderPostalCode: '請輸入郵遞區號',
address: '詳細地址',
placeholderAddress: '請輸入詳細地址',
city: '城市',
placeholderCity: '請輸入城市',
default: '默認',
},
// HTTP 錯誤狀態碼
httpError: {
400: '請求參數錯誤',
......@@ -220,5 +341,5 @@ export default {
// 自定義表單欄位
...customFieldsZhTW,
// 頁面標題
...pageFieldsZhTW,
...pageTitleZhTW,
};
\ No newline at end of file
......@@ -41,9 +41,31 @@ const router = createRouter({
component: () => import ('../views/personalCenter/myCoupon.vue')
},
{
path: '/accountCenter/:type?',//账号中心
path: '/accountCenter',//账号中心
meta: { title: "page.accountCenter" },
component: () => import ('../views/personalCenter/accountCenter.vue')
component: () => import ('../views/personalCenter/accountCenter.vue'),
children: [
{
path: '/basicInfor',//基础资料
meta: { title: "page.basicInfor" },
component: () => import ('../views/personalCenter/accountPage/basicInfor.vue')
},
{
path: '/account/:reType?',//账户
meta: { title: "page.account" },
component: () => import ('../views/personalCenter/accountPage/account.vue')
},
{
path: '/passengerList',//乘客列表
meta: { title: "page.passengerList" },
component: () => import ('../views/personalCenter/accountPage/passengerList.vue')
},
{
path: '/mailingAddressList',//邮寄地址列表
meta: { title: "page.mailingAddressList" },
component: () => import ('../views/personalCenter/accountPage/mailingAddressList.vue')
},
]
},
{
path: '/distributionCenter',//分销中心
......@@ -55,22 +77,22 @@ const router = createRouter({
{
path: '/resetPassword',
meta: { title: "page.resetPassword" },
component: () => import ('../views/personalCenter/accountCenter/resetPassword.vue')
component: () => import ('../views/personalCenter/accountPage/resetPassword.vue')
},
{
path: '/editEmail/:forward?',
meta: { title: "page.editEmail" },
component: () => import ('../views/personalCenter/accountCenter/editEmail.vue')
component: () => import ('../views/personalCenter/accountPage/editEmail.vue')
},
{
path: '/perForgePassword/:email?',
meta: { title: "page.editEmail" },
component: () => import ('../views/personalCenter/accountCenter/perForgePassword.vue')
component: () => import ('../views/personalCenter/accountPage/perForgePassword.vue')
},
]
},
{
path: "/login",
path: "/login/:reType?",
name: "login",
component: () => import("../views/auth/Login.vue"),
meta: { title: "page.login" },
......
This diff is collapsed.
......@@ -184,9 +184,9 @@ export const useSystemConfigStore = defineStore('systemConfig', {
// 保存租户信息
this.tenantId = data.tenantId || null
this.domainName = data.config.domainName || null
this.distributorId = data.distributorId ?? 0
this.navs = data.navList || []
this.domainName = data.config?.domainName || null
this.distributorId = data.distributorId || 0
// 保存平台配置
this.platformConfig = data.platform || null
......
......@@ -213,7 +213,55 @@ export const useUserStore = defineStore('user', {
}
} catch (error: any) {
console.error('Google login error:', error)
ResultMessage.Error(error.message|| i18n.global.t('login.wechatLoginFailed'))
ResultMessage.Error(error.message|| i18n.global.t('login.wechatBindFailed'))
return {
status: 'ERROR',
verify: false
}
}
},
/**
* Line登录
* @param tenantId 租户ID
*/
async setUserLineLoginAsync(tenantId: string, code: string,distributorId: number,parentId?: any,redirectUri?: string): Promise<UserLoginResult> {
try {
const response = await UserService.lineLoginAsync(tenantId, code,distributorId,parentId,redirectUri)
console.log('Google login response:', response)
this.token = response.token || ''
this.userInfo = response.userInfo || {}
this.memberData = response.memberData
return {
status: 'SUCCESS',
verify: false,
data: [response]
}
} catch (error: any) {
console.error('Google login error:', error)
ResultMessage.Error(error.message|| i18n.global.t('login.lineLoginFailed'))
return {
status: 'ERROR',
verify: false
}
}
},
/**
* Line绑定
* @param tenantId 租户ID
*/
async setUserLineBindAsync(tenantId: string, code: string,distributorId: number,parentId?: any,redirectUri?: string): Promise<UserLoginResult> {
try {
const response = await UserService.lineBindAsync(tenantId, code,distributorId,parentId,redirectUri)
return {
status: 'SUCCESS',
verify: false,
data: [response]
}
} catch (error: any) {
console.error('Google login error:', error)
ResultMessage.Error(error.message|| i18n.global.t('login.lineBindFailed'))
return {
status: 'ERROR',
verify: false
......
......@@ -448,3 +448,16 @@ export function query (url?:string){
return json
}
/**
* 邮箱脱敏
* @param email 邮箱地址
* @returns 脱敏后的邮箱地址
* @example
*/
export function maskEmail(email: string): string {
if (!email.includes('@')) return email;
let [localPart, domain] = email.split('@');
let prefix = localPart.length > 5 ? localPart.substring(0, 5) : localPart;
return prefix + '****@' + domain;
}
import i18n from '@/i18n'
// 优惠券类型枚举
// 优惠券状态枚举
const CouponTypeEnum = Object.freeze({
UNUSED: {
value: 1,
......
<template>
<div class="fixed-center text-center">
<p>
<img
src="~assets/sad.svg"
style="width:30vw;max-width:150px;"
>
</p>
<p class="text-faded">
Sorry, nothing here...<strong>(404)</strong>
</p>
<q-btn
color="secondary"
style="width:200px;"
to="/"
label="Go back"
/>
</div>
</template>
<script >
</script>
......@@ -59,19 +59,6 @@
</a-form>
</a-space>
<!-- Line登录 -->
<div v-if="loginMsg.reType==3" class="login-form-content scan-content">
<div class="qr-container">
<div class="qr-box line-qr-box">
<div class="qr-code-placeholder">
<i class="ki-outline ki-message-text"></i>
</div>
</div>
<p class="scan-instruction">{{ t('login.scanTip') }}</p>
<p class="scan-status">{{ t('login.scanWaiting') }}</p>
</div>
</div>
<div class="mt-[40px] flex items-center justify-center">
<a-divider orientation="center" class="text-[16px] text-[#EEEFEB]"></a-divider>
<span class="text-nowrap customPrimary-5 px-[14px]">{{ t('login.othenLogin') }}</span>
......@@ -152,12 +139,13 @@ const systemConfigStore = useSystemConfigStore()
const loading = ref(true)
const router = useRouter()
const { params } = router.currentRoute.value
const googleButtonContainer = ref(null);
const loginMsg = reactive({
tenantId: systemConfigStore.tenantId || null,
reType: 0,//登录方式 0账号密码 1谷歌授权 3LINE授权 7FaceBook授权
reType: 0,//登录方式 0账号密码 1谷歌授权 2微信授权 3LINE授权 7FaceBook授权
openId: "",
email: "",//2310721242@qq.com
password: '',//yj123456
......@@ -165,6 +153,7 @@ const loginMsg = reactive({
parentId: null,
redirectUri: '',
})
if(params&&params.reType) loginMsg.reType = Number(params.reType)
const loginMethods = ref([
{
......@@ -213,31 +202,49 @@ const rules = computed(() => ({
const openInfo = ref({} as any)
// 获取微信登录AppID 域名 重定向页面
const getAppIdRedirectUri = async () => {
// const { AppID, State, OpenRedirectUri } = openInfo.value;
// let redirect_uri = OpenRedirectUri;
// const url = `https://open.weixin.qq.com/connect/qrconnect?appid=${AppID}&redirect_uri=${encodeURIComponent('https://www.oytour.com/#/login')}&response_type=code&scope=snsapi_login&state=${State}&wechat_redirect=${redirect_uri}`;
// window.location.href = url;
// return
// 随机生成 state 参数
const generateState = () => {
return Math.random().toString(36).substring(2); // 简单的生成随机字符串
}
// line授权登录
const loginWithLine = () => {
const channelId = openInfo.value.appId; // 替换为你的 LINE Channel ID
const redirectUri = encodeURIComponent(openInfo.value.redirectUri || 'http://localhost:8002/login/3'); // 替换为你的重定向 URI
const state = generateState(); // 防止 CSRF 攻击,生成随机的 state 参数
// 构造 LINE 授权 URL
const lineLoginUrl = `https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id=${channelId}&redirect_uri=${redirectUri}&state=${state}&scope=openid%20profile`;
// 跳转到 LINE 登录页面
window.location.href = lineLoginUrl;
}
// line登录
const useLineLogin = async(code:string) => {
const redirectUri = window.location.origin+window.location.pathname
loading.value = true
try {
// 获取微信登录AppID 域名
const response = await userStore.getUserWechatAppIdAsync(loginMsg.tenantId?.toString() || '',providerTypeEnum.WECHAT.value)
const response = await userStore.setUserLineLoginAsync(loginMsg.tenantId?.toString() || '', code, loginMsg.distributorId,loginMsg.parentId,redirectUri)
if (response.status == 'SUCCESS') {
openInfo.value = response.data[0]
const { appId, redirectUri } = openInfo.value;
const redirect_url = redirectUri?redirectUri:'https://www.oytour.com/#/login'
const url = `https://open.weixin.qq.com/connect/qrconnect?appid=${appId}&redirect_uri=${encodeURIComponent(redirect_url)}&response_type=code&scope=snsapi_login&state=${1}&wechat_redirect=${redirect_url}`;
window.location.href = url;
}
userStore.setLoginType(loginMsg.reType || 0)
Message.success(t('login.loginSuccess'))
const forward = localStorage.getItem('forward')
localStorage.removeItem('forward')
router.push({
path: forward ? forward : '/',
})
}else if(response.status == 'ERROR'){
router.push('/login')
}
}catch (error: any) {
Message.error(error.message)
} finally {
loading.value = false
}
}
// 微信授权登录
const loginWechat = () => {
const redirect_url = openInfo.value.redirectUri || 'http://localhost:8002/login/2'
const url = `https://open.weixin.qq.com/connect/qrconnect?appid=${openInfo.value.appId}&redirect_uri=${encodeURIComponent(redirect_url)}&response_type=code&scope=snsapi_login&state=${1}&wechat_redirect=${redirect_url}`;
window.location.href = url;
}
// 微信登录
const useWechatLogin = async(code:string) => {
......@@ -252,6 +259,36 @@ const useWechatLogin = async(code:string) => {
router.push({
path: forward ? forward : '/',
})
}else if(response.status == 'ERROR'){
router.push('/login')
}
}catch (error: any) {
Message.error(error.message)
} finally {
loading.value = false
}
}
// 获取AppID 域名 重定向页面
const getAppIdRedirectUri = async (providerType: string) => {
// const { AppID, State, OpenRedirectUri } = openInfo.value;
// let redirect_uri = OpenRedirectUri;
// const url = `https://open.weixin.qq.com/connect/qrconnect?appid=${AppID}&redirect_uri=${encodeURIComponent('https://www.oytour.com/#/login')}&response_type=code&scope=snsapi_login&state=${State}&wechat_redirect=${redirect_uri}`;
// window.location.href = url;
loading.value = true
try {
// 获取微信绑定AppID 域名
const response = await userStore.getUserWechatAppIdAsync(loginMsg.tenantId?.toString() || '',providerType)
if (response.status == 'SUCCESS') {
openInfo.value = response.data[0]
if(openInfo.value.appId){
if(providerType==providerTypeEnum.WECHAT.value){
loginWechat()
}else if(providerType==providerTypeEnum.GOOGLE.value){
renderGoogleButton()
}else if(providerType==providerTypeEnum.LINE.value){
loginWithLine()
}
}
}
}catch (error: any) {
Message.error(error.message)
......@@ -265,9 +302,10 @@ const handleClick = (path: string) => {
}
const toggleLoginType = (type: number) => {
if(type==3||type==7) return Message.error(t('login.loginTypeNotSupport'))
if(type==7) return Message.error(t('login.loginTypeNotSupport'))
loginMsg.reType = type
if(type==2) { getAppIdRedirectUri() }
if(type==3) getAppIdRedirectUri(providerTypeEnum.LINE.value)
if(type==2) getAppIdRedirectUri(providerTypeEnum.WECHAT.value)
// if(type==1) {
// loginWithGoogle()
// }
......@@ -279,12 +317,11 @@ const loginHandler = async ({ values, errors }: any) => {
await handleLogin()
}
// 手动构建授权URL(适用于无第三方库的简单场景)
const clientId = '532164762940-vk65sge5jab1eq8mgbv1srh672ehnkff.apps.googleusercontent.com';
const redirectUri = encodeURIComponent('https://www.oytour.com/#/login?reType=1'); // 必须与Google控制台配置的一致
const scope = encodeURIComponent('profile email'); // 请求的权限范围
const loginWithGoogle = () => {
// 手动构建授权URL(适用于无第三方库的简单场景)
const clientId = openInfo.value.appId //'532164762940-vk65sge5jab1eq8mgbv1srh672ehnkff.apps.googleusercontent.com';
const redirectUri = encodeURIComponent(openInfo.value.redirectUri || 'http://localhost:8002/login/1'); // 必须与Google控制台配置的一致
const scope = encodeURIComponent('profile email'); // 请求的权限范围
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&access_type=offline`;
window.location.href = authUrl;
};
......@@ -294,7 +331,7 @@ const renderGoogleButton = () => {
try {
// 渲染 Google 登录按钮
window.google.accounts.id.initialize({
client_id: '532164762940-vk65sge5jab1eq8mgbv1srh672ehnkff.apps.googleusercontent.com',
client_id: openInfo.value.appId,// '532164762940-vk65sge5jab1eq8mgbv1srh672ehnkff.apps.googleusercontent.com'
callback: handleSignInSuccess,
error_callback: handleSignInError,
ux_mode: 'popup' // 可选:popup/redirect
......@@ -328,7 +365,9 @@ const handleSignInSuccess = async (googleUser:any) => {
router.push({
path: forward ? forward : '/',
})
}
}else if(response.status == 'ERROR'){
router.push('/login')
}
}catch (error: any) {
Message.error(error.message || t('login.googleLoginFailed'))
} finally {
......@@ -399,17 +438,17 @@ onMounted(async () => {
})
const queryParams = query()
const code = queryParams.code
if (code) {
loginMsg.reType = 2
useWechatLogin(code)
if(loginMsg.reType==2) useWechatLogin(code)
else if(loginMsg.reType==3) useLineLogin(code)
}
try {
await initGoogleSDK();
// 确保 SDK 加载完成后渲染按钮
await new Promise(resolve => setTimeout(resolve));
renderGoogleButton()
getAppIdRedirectUri(providerTypeEnum.GOOGLE.value)
} catch (error) {
console.error('SDK 初始化失败:', error);
......
......@@ -234,7 +234,7 @@ const goPage = (path:string) => {
router.push(path)
return
}
router.push('/accountCenter')
router.push('/basicInfor')
}
// 验证邮箱是否可用
......@@ -366,16 +366,16 @@ const handleSubmit = async () => {
loading.value = true
try {
const response = await UserService.resetPasswordAsync(formData.tenantId || 'default',formData.email,formData.code,formData.newPassword)
console.log(response,'response')
Message.success(t('login.resetSuccess'))
// 延迟跳转到登录页
setTimeout(() => {
if(params&&params.email){
router.push('/accountCenter')
}else{
router.push('/login')
}
}, 2000)
if(response){
Message.success(t('login.resetSuccess'))
// 延迟跳转到登录页
if(params&&params.email){
router.push('/basicInfor')
}else{
router.push('/login')
}
}
} catch (error: any) {
Message.error(error.message || t('login.resetFailed'))
} finally {
......
......@@ -332,7 +332,7 @@ const rules = computed(() => ({
const loginPage = ref(null)
const currentStep = ref(1)
const currentStep = ref(2)
const loading = ref(true)
const isFromOta = ref(false) // 是否从 OTA 授权进入
......@@ -671,7 +671,7 @@ const init = async () => {
}
}
init()
// init()
getSimples()
......
......@@ -5,30 +5,24 @@
<div class="flex pl-[13px]">
<div v-for="(item,index) in TitleBars"
class="myOrder-status mr-[42px] py-[22px] cursor-pointer relative"
:class="[current==item.value?'active font-medium':'font-light',index?'ml-[42px]':null]"
@click="changeStatus(item.value)">{{ item.label }}
:class="[activeMenu==item.key?'active font-medium':'font-light',index?'ml-[42px]':null]"
@click="changeStatus(item.path)">{{ item.label }}
<div class="myOrder-status-border absolute left-0 bottom-0 w-full flex justify-center">
<div></div>
</div>
</div>
</div>
<a-divider class="!m-[0]"/>
<a-scrollbar v-if="current<3" class="max-h-[735px] mt-[20px] overflow-auto"
ref="scrollContainer">
<basicInfor v-if="current==1"></basicInfor>
<account v-if="current==2"></account>
</a-scrollbar>
<div v-else class="max-h-[735px] mt-[20px]">
<passengerList v-if="current==3"></passengerList>
<mailingAddressList v-if="current==4"></mailingAddressList>
</div>
<main class="max-h-[735px] mt-[20px] overflow-hidden">
<router-view />
</main>
</a-spin>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useRouter,useRoute } from 'vue-router'
import account from "./components/accountCenter/account.vue"
import basicInfor from "./components/accountCenter/basicInfor.vue"
import passengerList from "./components/accountCenter/passengerList.vue"
......@@ -36,35 +30,47 @@ import mailingAddressList from "./components/accountCenter/mailingAddressList.vu
const { t } = useI18n();
const router = useRouter()
const route = useRoute()
const { params } = router.currentRoute.value
const loading = ref(true)
const current = ref(1)
if(params&&params.type){
current.value = Number(params.type)
}
const TitleBars = [
{
label: t('personal.basicInfo'),
value: 1
},
value: 1,
path: '/basicInfor',
key: 'basicInfor'
},
{
label: t('personal.accountInfor'),
value: 2
value: 2,
path: '/account',
key: 'account'
},
{
label: t('personal.commonPassenger'),
value: 3
value: 3,
path: '/passengerList',
key: 'passengerList'
},
{
label: t('personal.postAddress'),
value: 4
value: 4,
path: '/mailingAddressList',
key: 'mailingAddressList'
}
]
const changeStatus = (value: number) => {
current.value = value
// 当前激活的菜单
const activeMenu = computed(() => {
const path = route.path
const menu = TitleBars.find(item => path.startsWith(item.path))
return menu?.key || ''
})
const changeStatus = (path: string) => {
router.push(path)
}
onMounted(async () => {
......
<template>
<div class="w-full">
<a-space direction="vertical" class="w-full">
<div class="w-full h-full overflow-auto">
<a-space direction="vertical" class="w-full">
<!-- horizonta -->
<a-form :model="formData" :rules="rules" layout="vertical" class="w-full">
<a-form :model="formData" :rules="rules" layout="vertical" class="w-full"
ref="formDataRef">
<a-row class="w-full">
<a-col :span="24">
<a-form-item field="" :label="t('personal.photo')">
<div class="flex items-center">
<div>
<div class="w-[100px] h-[100px] rounded-full bg-[#f5f5f5] border-[2px] border-[#E3E6DA]">
<img :src="formData.photo" class="w-full h-full rounded-full" alt="avatar">
<div class="w-[100px] h-[100px] rounded-full bg-[#FFFFFF] border-[2px] border-[#E3E6DA]">
<a-avatar :size="96"
class="w-full h-full rounded-full" alt="avatar" >
<img v-if="formData?.photo"
alt="avatar"
:src="formData.photo"
/>
<img v-else class="w-full h-full cursor-pointer"
alt="avatar"
src="../../../assets/images/personal/avatar.png"
/>
</a-avatar>
</div>
</div>
<div class="ml-[37px]">
......@@ -54,7 +65,7 @@
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="sex" :label="t('personal.gender')">
<a-form-item field="sex" :label="t('personal.gender')" required>
<a-select class="formData-input !w-[289px] mr-[30px]"
v-model="formData.sex"
size="large"
......@@ -87,7 +98,24 @@
</a-select>
</a-form-item>
</a-col>
<a-col :span="8"></a-col>
<a-col :span="8">
<a-form-item field="wechatId" :label="t('login.bindingWechat')" :required="!formData?.lineId">
<a-input class="formData-input !w-[289px] mr-[30px]"
v-model="formData.wechatId"
size="large"
:placeholder="t('login.lineIdOrWechat')">
</a-input>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="lineId" label="Line" :required="!formData?.wechatId">
<a-input class="formData-input !w-[289px] mr-[30px]"
v-model="formData.lineId"
size="large"
:placeholder="t('login.lineIdOrWechat')">
</a-input>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item field="phoneCode" :label="t('login.phoneCode')">
<a-select class="formData-input !w-[289px] mr-[30px]"
......@@ -151,18 +179,18 @@
</a-row>
</a-form>
</a-space>
<!-- 图片预览 -->
<a-image-preview
v-model:visible="previewVisible"
:src="previewUrl"
:actions-layout="['rotateLeft', 'rotateRight', 'zoomIn', 'zoomOut', 'originalSize']"
/>
</a-form>
</a-space>
<!-- 图片预览 -->
<a-image-preview
v-model:visible="previewVisible"
:src="previewUrl"
:actions-layout="['rotateLeft', 'rotateRight', 'zoomIn', 'zoomOut', 'originalSize']"
/>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, watch, computed, provide } from "vue";
import { reactive, ref, watch, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useRouter } from 'vue-router'
import { Message } from '@arco-design/web-vue'
......@@ -187,11 +215,11 @@ const AreaCodeList = ref([])
const genderOptions = ref([
{
label: t('ORDER.GENDER_MALE'),
value: 'MALE'
value: 1
},
{
label: t('ORDER.GENDER_FEMALE'),
value: 'FEMALE'
value: 2
},
])
const tenantId = systemConfigStore.tenantId
......@@ -206,6 +234,7 @@ const formData = reactive({
residentialArea: null as any,//国籍
sex: null as any,//性别 1男 2女
wechatId: null as any,//微信ID
lineId: null as any,//微信ID
})
......@@ -237,6 +266,8 @@ const maxSize = 2 // 2MB
const previewVisible = ref(false)
const previewUrl = ref('')
const formDataRef = ref<any>()
watch(() => userStore.personalInfor, (newVal, oldVal) => {
if(newVal!=oldVal){
userInfor.value = newVal
......@@ -252,7 +283,7 @@ const getPersonalInfor = async () => {
const validateFileSize = (file: File): boolean => {
const sizeMB = file.size / 1024 / 1024
if (sizeMB > maxSize) {
Message.error(`图片大小不能超过 ${maxSize}MB`)
Message.error(t('personal.photoSize', { size: maxSize }))
return false
}
return true
......@@ -261,7 +292,7 @@ const validateFileSize = (file: File): boolean => {
// 处理图片预览
const handlePreview = (fileItem: FileItem) => {
if (!fileItem.url) {
Message.warning('图片还未上传完成')
Message.warning(t('personal.uploadComplete'))
return
}
previewUrl.value = fileItem.url
......@@ -305,11 +336,11 @@ const uploadFile = async (fileItem: FileItem) => {
formData.photo = url
} else {
fileItem.status = 'error'
Message.error('上传失败')
Message.error(t('personal.uploadFailed'))
}
} catch (error) {
fileItem.status = 'error'
Message.error('上传失败')
Message.error(t('personal.uploadFailed'))
console.error('上传失败:', error)
}
}
......@@ -319,7 +350,7 @@ const handleAreaCodeChange = (value: string) => {
formData.phoneCode = `${value.indexOf('+')>-1?value:('+'+value)}`
}
const initPullDown = () =>{
const initData = () =>{
getUserDetail()
getSimples()
}
......@@ -337,9 +368,15 @@ const getSimples = async () => {
const verification = () => {
let msg = ''
// 简单验证必填字段
if (msg==''&&(formData.name==''||!formData.name||userInfor.value.surName==''||!formData.surName)) {
if (msg==''&&(formData.name==''||!formData.name
||formData.surName==''||!formData.surName
||formData.sex==''||!formData.sex)) {
msg = t('login.pleaseComplete')
}
if (msg==''&&(formData.lineId==''||!formData.lineId)
&&(formData.wechatId==''||!formData.wechatId)) {
msg = t('login.lineIdOrWechat')
}
return msg
}
......@@ -349,15 +386,16 @@ const handleSubmit = async () => {
Message.warning(msg)
return
}
formDataRef.value.clearValidate()
loading.value = true
try {
const registerData = formData
const response = await UserService.updateMemberUser(tenantId|| 'default',registerData)
if (response) {
Message.success(t('personal.updateSuccess'))
getUserDetail()
getPersonalInfor()
// userStore.updateProfile({photo:formData.photo,name:formData.name})
}
}
} catch (error: any) {
console.error('提交失败:', error)
Message.error(error.message)
......@@ -374,13 +412,12 @@ const getUserDetail = async () => {
formData.name = response.name || ''
formData.surName = response.surName || ''
formData.birthday = response.birthday || null
formData.isReceivePush = response.isReceivePush || null
formData.lineId = response.lineId || null
formData.phone = response.phone || null
formData.phoneCode = response.phoneCode || null
formData.phone = response.phone || ''
formData.phoneCode = response.phoneCode || ''
formData.photo = response.photo || null
formData.residentialArea = response.residentialArea || null
formData.sex = response.sex || null
formData.sex = response.sex || 1
formData.wechatId = response.wechatId || null
}
} catch (error) {
......@@ -388,7 +425,7 @@ const getUserDetail = async () => {
}
}
initPullDown()
initData()
</script>
<style scoped lang="scss">
:deep(.arco-form-item-content){
......
......@@ -11,7 +11,7 @@
<a-form-item field="" label="" class="">
<div class="flex justify-center relative">
<div class="flex justify-center items-center absolute left-[-300px] top-[5px]"
@click="goPage('/accountCenter/2')">
@click="goPage('/account')">
<div class="flex items-center justify-center cursor-pointer">
<icon-left class="font-semibold" size="20" strokeLinejoin="miter" />
<span class="text-base ml-[9px]">{{ t('personal.return') }}</span>
......@@ -438,7 +438,7 @@ const handleSubmit = async () => {
if(params&&params.forward){
goPage(`/${params.forward}`)
}else{
goPage('/accountCenter/2')
goPage('/account')
}
}
} catch (error: any) {
......
......@@ -8,18 +8,18 @@
border-[1px] mb-[18px] flex items-center
justify-between py-[15px] pl-[36px] pr-[22px]"
v-for="(item,index) in dataList">
<div class="w-[100px]">
<div class="w-[100px] mr-[20px]">
{{ item.name }}
</div>
<div class="w-[200px]">
<div class="flex-1 mr-[20px]">
{{ item.cityName }}{{ item.address }}
</div>
<div class="w-[150px]">
<div class="w-[200px] mr-[20px]">
{{item.phoneCode}} {{ item.phone }}
</div>
<div class="w-[100px]"
:class="[item.isDefault?'customColor-text-5':'']">
<a-switch v-if="item.isDefault" v-model="item.isDefault" :checked-value="Number(1)" :unchecked-value="Number(0)" />
<!-- <a-switch v-if="item.isDefault" v-model="item.isDefault" :checked-value="Number(1)" :unchecked-value="Number(0)" disabled/> -->
{{ item.isDefault?t('personal.default'):'' }}
</div>
<div class="flex items-center justify-between">
......@@ -57,7 +57,7 @@
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item :label="t('personal.city')" field="cityName" required>
<a-form-item :label="t('personal.city')" field="cityName">
<a-input class="formData-input !w-[289px] mr-[30px]"
v-model="formData.cityName"
size="large"
......@@ -232,7 +232,7 @@ const handleDivScroll = (e: any) => {
}
}
const initPullDown = () =>{
const initData = () =>{
loadData()
getSimples()
}
......@@ -325,12 +325,12 @@ const cancel = () => {
const verification = () => {
let msg = ''
// ||formData.cityName==''||!formData.cityName
if (msg==''&&(formData.name==''||!formData.name
||formData.phoneCode==''||!formData.phoneCode
||formData.phone==''||!formData.phone
||formData.countryId==''||!formData.countryId
||formData.cityName==''||!formData.cityName
||formData.address==''||!formData.address
||formData.phoneCode==''||!formData.phoneCode
||formData.phone==''||!formData.phone
)) {
msg = t('login.pleaseComplete')
}
......@@ -357,7 +357,7 @@ const handleSubmit = async () => {
}
}
initPullDown()
initData()
</script>
<style scoped lang="scss">
......@@ -404,21 +404,21 @@ initPullDown()
.editIcon{
width: 16px;
height: 16px;
background: url('../../../../assets/images/personal/pen_2.png')no-repeat;
background: url('../../../assets/images/personal/pen_2.png')no-repeat;
background-size: 100% 100%;
}
.editIcon:hover{
background: url('../../../../assets/images/personal/pen.png')no-repeat;
background: url('../../../assets/images/personal/pen.png')no-repeat;
background-size: 100% 100%;
}
.deleteIcon{
width: 16px;
height: 16px;
background: url('../../../../assets/images/personal/sc_2.png')no-repeat;
background: url('../../../assets/images/personal/sc_2.png')no-repeat;
background-size: 100% 100%;
}
.deleteIcon:hover{
background: url('../../../../assets/images/personal/sc_1.png')no-repeat;
background: url('../../../assets/images/personal/sc_1.png')no-repeat;
background-size: 100% 100%;
}
.addPassenger{
......@@ -447,4 +447,7 @@ initPullDown()
.cancelbuttonBox:hover{
background-color: var(--customPrimary-7);
}
:deep(.arco-scrollbar-track-direction-vertical){
display: none;
}
</style>
\ No newline at end of file
<template>
<a-spin :loading="loading" class="w-full">
<div v-show="!showType">
<a-scrollbar class="max-h-[692px] overflow-auto"
<a-scrollbar class="max-h-[692px]"
@scroll="handleDivScroll"
ref="scrollContainer">
<div class="accountCenter rounded-[14px]
......@@ -194,11 +194,11 @@ const loading = ref(true)
const genderOptions = ref([
{
label: t('ORDER.GENDER_MALE'),
value: 'MALE'
value: 1
},
{
label: t('ORDER.GENDER_FEMALE'),
value: 'FEMALE'
value: 2
},
])
const formData = reactive({
......@@ -209,7 +209,7 @@ const formData = reactive({
phone: null as any,//手机号
phoneCode: null as any,//手机号国家码
countryId: null as any,//国籍
sex: null as any,//性别 1男 2女
sex: 1 as any,//性别 1男 2女
idCard: null as any,//身份证号
email: null as any,//邮箱
})
......@@ -262,7 +262,7 @@ const handleDivScroll = (e: any) => {
}
}
const initPullDown = () =>{
const initData = () =>{
loadData()
getSimples()
}
......@@ -345,10 +345,10 @@ const cancel = () => {
formData.name = ''
formData.surName = ''
formData.birthday = null
formData.phone = null
formData.phoneCode = null
formData.phone = ''
formData.phoneCode = ''
formData.countryId = null
formData.sex = null
formData.sex = 1
formData.idCard = null
formData.email = null
}
......@@ -375,6 +375,7 @@ const handleSubmit = async () => {
try {
const registerData = formData
const response = await UserService.updateMemberGuest(systemConfig.tenantId || 'default',registerData)
console.log('提交成功:', response)
showType.value = null
resetQuery()
} catch (error: any) {
......@@ -385,7 +386,7 @@ const handleSubmit = async () => {
}
}
initPullDown()
initData()
</script>
<style scoped lang="scss">
......@@ -432,21 +433,21 @@ initPullDown()
.editIcon{
width: 16px;
height: 16px;
background: url('../../../../assets/images/personal/pen_2.png')no-repeat;
background: url('../../../assets/images/personal/pen_2.png')no-repeat;
background-size: 100% 100%;
}
.editIcon:hover{
background: url('../../../../assets/images/personal/pen.png')no-repeat;
background: url('../../../assets/images/personal/pen.png')no-repeat;
background-size: 100% 100%;
}
.deleteIcon{
width: 16px;
height: 16px;
background: url('../../../../assets/images/personal/sc_2.png')no-repeat;
background: url('../../../assets/images/personal/sc_2.png')no-repeat;
background-size: 100% 100%;
}
.deleteIcon:hover{
background: url('../../../../assets/images/personal/sc_1.png')no-repeat;
background: url('../../../assets/images/personal/sc_1.png')no-repeat;
background-size: 100% 100%;
}
.addPassenger{
......@@ -475,4 +476,7 @@ initPullDown()
.cancelbuttonBox:hover{
background-color: var(--customPrimary-7);
}
:deep(.arco-scrollbar-track-direction-vertical){
display: none;
}
</style>
\ No newline at end of file
......@@ -11,7 +11,7 @@
<a-form-item field="" label="" class="">
<div class="flex justify-center relative">
<div class="flex justify-center items-center absolute left-[-300px] top-[5px]"
@click="currentStep==1?goPage('/accountCenter/2'):handlePrevious()">
@click="currentStep==1?goPage('/account'):handlePrevious()">
<div class="flex items-center justify-center cursor-pointer">
<icon-left class="font-semibold" size="20" strokeLinejoin="miter" />
<span class="text-base ml-[9px]">{{ currentStep==1?t('personal.return'):t('personal.returnStep') }}</span>
......@@ -255,7 +255,7 @@ const goPage = (path:string) => {
router.push(path)
return
}
router.push('/accountCenter/2')
router.push('/account')
}
// 验证邮箱是否可用
......@@ -387,13 +387,12 @@ const handleSubmit = async () => {
loading.value = true
try {
const response = await UserService.resetPasswordAsync(formData.tenantId || 'default',formData.email,formData.code,formData.newPassword)
console.log(response,'response')
Message.success(t('login.resetSuccess'))
// currentStep.value ++
// 延迟跳转
setTimeout(() => {
router.push('/accountCenter/2')
}, 2000)
if(response){
Message.success(t('login.resetSuccess'))
// currentStep.value ++
// 延迟跳转
router.push('/account')
}
} catch (error: any) {
Message.error(error.message || t('login.resetFailed'))
} finally {
......
......@@ -12,7 +12,7 @@
<a-form-item field="" label="" class="">
<div class="flex justify-center relative">
<div class="flex justify-center items-center absolute left-[-300px] top-[5px]"
@click="goPage('/accountCenter/2')">
@click="goPage('/account')">
<div class="flex items-center justify-center cursor-pointer">
<icon-left class="font-semibold" size="20" strokeLinejoin="miter" />
<span class="text-base ml-[9px]">{{ t('personal.return') }}</span>
......@@ -368,12 +368,11 @@ const handleSubmit = async () => {
loading.value = true
try {
const response = await UserService.setNewPasswordAsync(formData.tenantId || 'default',formData.oldPassword,formData.newPassword)
console.log(response,'response')
Message.success(t('login.resetSuccess'))
// 延迟跳转到登录页
setTimeout(() => {
goPage('/accountCenter/2')
}, 2000)
if(response){
Message.success(t('login.resetSuccess'))
// 延迟跳转到登录页
goPage('/account')
}
} catch (error: any) {
Message.error(error.message || t('login.resetFailed'))
} finally {
......
......@@ -2,16 +2,21 @@
<div class="flex flex flex-col w-[198px]">
<div class="h-full bg-[#F9F9F7] rounded-[14px]">
<div class="mt-[37px] flex justify-center items-center">
<a-avatar class="LeftViewImg cursor-pointer flex-shrink-0 !w-[80px] !h-[80px]">
<img class="w-full h-full cursor-pointer"
<a-avatar class="LeftViewImg cursor-pointer flex-shrink-0 !w-[80px] !h-[80px]"
:size="80">
<img v-if="userInfo?.photo"
alt="avatar"
:src="userInfo?.photo || systemConfigStore?.config?.logo || 'https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp'"
:src="userInfo.photo"
/>
<img v-else
alt="avatar"
src="../../../assets/images/personal/avatar.png"
/>
</a-avatar>
</div>
<div class="mt-[13px] text-lg font-medium text-center truncate">{{ userInfo?.name || '' }}</div>
<div class="flex justify-center items-center mt-[10px] cursor-pointer"
@click="goPage('/accountCenter')">
@click="goPage('/basicInfor')">
<span v-if="!userInfo?.IsComplete" class="LeftViewTisp w-[6px] h-[6px] rounded-full"></span>
<span class="LeftViewData ml-[5px] text-sm font-medium text-[#666]">
{{ t('personal.completeProfile') }}
......@@ -170,4 +175,7 @@ getPersonalInfor()
/* background-color: rgb(var(--arcoblue-7)); */
color: rgb(var(--arcoblue-6));
}
:deep(.arco-badge-number){
box-shadow: 0 0 0 0px rgba(255, 151, 7, 0);
}
</style>
\ No newline at end of file
......@@ -13,7 +13,7 @@
</div>
</template>
<script setup lang="ts">
import { ref, computed,reactive } from 'vue'
import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import LeftView from './components/LeftView.vue'
......@@ -27,6 +27,9 @@ const route = useRoute()
const activeMenu = computed(() => {
const path = route.path
const menu = menuList.value.find(item => path.startsWith(item.path))
if(path=='/account'||path=='/passengerList'||path=='/mailingAddressList'){
return 'basicInfor'
}
return menu?.key || 'myOrder'
})
......@@ -55,8 +58,8 @@ const menuList = ref([
},
{
name: t('personal.menu.accountCenter'),
path: '/accountCenter',
key: 'accountCenter',
path: '/basicInfor',
key: 'basicInfor',
},
{
name: t('personal.menu.distributionCenter'),
......
This diff is collapsed.
......@@ -31,28 +31,79 @@
class="myOrder-status mr-[45px] py-[22px] cursor-pointer relative"
:class="[currentStatus==item.value?'active font-medium':'font-light',index?'ml-[45px]':null]"
@click="changeStatus(item.value)">
{{ item.lable }}
<span class="pr-[10px]">{{ item.lable }}</span>
<template v-if="item.badge">({{ item.badge }})</template>
<div class="myOrder-status-border absolute left-0 bottom-0 w-full flex justify-center">
<div></div>
</div>
</div>
</div>
<a-divider class="!m-[0]"/>
<a-scrollbar class="max-h-[615px] overflow-auto"
<a-scrollbar class="flex max-h-[733px] overflow-auto mt-[23px]"
:class="[dataList.length==0&&!loading?'h-[733px] tems-center justify-center':'']"
@scroll="handleDivScroll"
ref="scrollContainer">
<div v-if="dataList.length" class="flex flex-wrap">
<a-space wrap direction="horizontal" size="medium">
<div class="myCouponItem w-[450px] h-[128px]
border-[1px] border-solid border-[#E5E5E5]
rounded-[8px] flex items-center
ml-[10px] mb-[10px]" v-for="(item,index) in dataList" :key="index">
<div class="w-[181px] flex flex-col justify-center items-center">
<div class="h-[16px] flex items-center justify-center customPrimary-6 mb-[10px]">
<div class="w-[4px]"><a-divider class="myCouponItemType" /></div>
<div class="text-base fontlight text-nowrap px-[5px]">门票</div>
<div class="w-[4px]"><a-divider class="myCouponItemType" /></div>
</div>
<div class="customPrimary-6">
<span class="text-base font-normal mr-[5px]">¥</span>
<span class="text-[2.875rem] font-bold">300</span>
<span class="text-base font-normal">{{ t('personal.discount') }}</span>
</div>
</div>
<div class="h-full relative">
<a-divider class="h-full myCouponItem-line" direction="vertical" type="dashed"></a-divider>
<div class="myCouponItem-top absolute top-[-5px] left-[8px] w-[10px] h-[9px] rounded-full bg-[#FFFFFF]">
&nbsp;
</div>
<div class="myCouponItem-bottom absolute bottom-[-5px] left-[8px] w-[10px] h-[9px] rounded-full bg-[#FFFFFF]">
&nbsp;
</div>
<div class="absolute top-[-6px] left-[8px] bg-[#FFFFFF] w-[10px] h-[4px] z-[2]"></div>
<div class="absolute bottom-[-8 px] left-[8px] bg-[#FFFFFF] w-[10px] h-[4px] z-[2]"></div>
</div>
<div class="flex-1 ml-[16px] mr-[28px] overflow-hidden">
<div class="mb-[8px] truncate">
景点门票满200减30景点门票满200减30景点门票满200减30
</div>
<div class="text-xs customColor-text-6 truncate">
{{ t('personal.validPeriod') }}:2025-11-07{{t('personal.to')}}2025-12-07
</div>
<div class="mt-[10px]">
<a-button type="primary" size="small" class="min-w-[97px] !h-[32px] !rounded-[8px] !text-xs font-light">
{{ t('personal.useCoupon') }}
</a-button>
</div>
</div>
</div>
</a-space>
</div>
<div v-else-if="dataList.length==0&&!loading" class="flex flex-col items-center justify-center">
<img class="w-[250px] h-[213px]" src="../../assets/images/personal/zwyhq.png" alt="" />
<span class="text-base SourceHanSansCN mt-[15px]">{{ t('personal.noCoupon') }}</span>
</div>
</a-scrollbar>
</a-spin>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref,reactive } from 'vue'
import { useI18n } from 'vue-i18n'
import { useUserStore } from '@/stores/user'
import { useSystemConfigStore } from '@/stores/index'
// 引入状态枚举
import { Message } from '@arco-design/web-vue'
import UserService from '@/services/UserService'
import CouponTypeEnum from '@/utils/couponTypeEnum'
// 引入产品类型枚举
import ListProductTypeEnum from '@/utils/listProductType'
......@@ -63,7 +114,6 @@ const systemConfigStore = useSystemConfigStore()
const TitleBars = ref<{value: number, lable: string}[]>([])
const currentStatus = ref(1)
const loading = ref(false)
// TitleBars.value.push({
// value: 0,
// lable: t('personal.orderStatus.ALL'),
......@@ -71,6 +121,7 @@ const loading = ref(false)
TitleBars.value.push({
value: CouponTypeEnum.UNUSED.value,
lable: CouponTypeEnum.UNUSED.desc,
badge: 18,
})
TitleBars.value.push({
value: CouponTypeEnum.USED.value,
......@@ -93,30 +144,60 @@ productTypeList.value.push({
label: ListProductTypeEnum.SCENIC.desc,
})
const queryParams = ref<any>({
pageIndex: 1,
pageSize: 10,
const queryParams = reactive({
SkipCount: 1,//跳过的记录数
MaxResultCount: 15,//最大结果数
orderStatus: currentStatus.value,
})
const dataList = ref<any>([])
const loding = ref(false)
const noMoreData = ref(false)
const noMoreData = ref(true)
const scrollContainer = ref<any>(null)
const scrollThreshold = 0 // 阈值,距离底部多少距离时触发加载
const totalCount = ref(100)//总记录数
const pageCount = ref(10)//总页数
const dataList = ref<any[]>([])
const loading = ref(true)
const changeStatus = (key: number) => {
currentStatus.value = key
}
const initData = () =>{
loadData()
}
const resetQuery = () => {
queryParams.SkipCount = 1
queryParams.Name = ''
dataList.value = []
noMoreData.value = true
loadData()
}
const loadData = async () => {
loading.value = true
try{
// const response = await UserService.memberMailingAddress(systemConfig.tenantId,queryParams)
// if(response){
// dataList.value = [...dataList.value,...(response.items || [])]
// if(queryParams.SkipCount==1){
// totalCount.value = response.totalCount || 0
// pageCount.value = Math.ceil(totalCount.value/queryParams.MaxResultCount) || 0
// }
// noMoreData.value = pageCount.value >= queryParams.SkipCount
// }
} catch (error: any) {
console.error('加载失败:', error)
Message.error(error.message)
} finally {
loading.value = false
}
}
// 处理 div 滚动事件(核心修改)
const handleDivScroll = (e: any) => {
if (loding.value || noMoreData.value) return;
if (loading.value || noMoreData.value) return;
const container = e.target;
if (!container) return;
// 计算滚动位置(兼容不同浏览器)
......@@ -124,13 +205,13 @@ const handleDivScroll = (e: any) => {
const clientHeight = container.clientHeight;
const scrollHeight = container.scrollHeight;
// 触底条件:滚动距离 + 可见高度 ≥ 总高度 - 阈值
if (scrollTop + clientHeight >= scrollHeight - scrollThreshold) {
if (scrollTop + clientHeight >= scrollHeight - scrollThreshold&&queryParams.SkipCount<pageCount.value) {
noMoreData.value = false
queryParams.value.pageIndex ++
queryParams.SkipCount ++
loadData(); // 加载下一页
}
}
initData()
</script>
<style scoped lang="scss">
.myOrder-status:hover{
......@@ -145,4 +226,28 @@ const handleDivScroll = (e: any) => {
.myOrder-status.active{
color: rbg(var(--gray-10));
}
.myCouponItem{
border-color: rgb(var(--arcoblue-4));
background: rgb(var(--arcoblue-11));
}
.myCouponItemType{
border-color: rgb(var(--arcoblue-6));
}
.myCouponItem-line{
border-color: rgb(var(--arcoblue-4));
}
.myCouponItem-top{
border-bottom: 1px solid rgb(var(--arcoblue-4));
border-left: 1px solid rgb(var(--arcoblue-4));
border-right: 1px solid rgb(var(--arcoblue-4));
}
.myCouponItem-bottom{
border-top: 1px solid rgb(var(--arcoblue-4));
border-left: 1px solid rgb(var(--arcoblue-4));
border-right: 1px solid rgb(var(--arcoblue-4));
}
:deep(.arco-scrollbar-track-direction-vertical){
display: none;
}
</style>
\ No newline at end of file
......@@ -11,7 +11,9 @@
{{ t('personal.bindingEmail')}}
</div>
<div class="mt-[10px] flex items-center">
<span class="text-base font-normal">{{ userInfor?.email || t('personal.unbound') }}</span>
<span class="text-base font-normal">
{{ maskEmail(userInfor?.email || t('personal.unbound')) }}
</span>
<img class="w-[15px] h-[15px] ml-[10px] cursor-pointer"
src="../../assets/images/personal/pen.png"
alt="" @click="editEmailClick"/>
......@@ -158,6 +160,7 @@ import OrderStatusEnum from '@/utils/orderStautsEnum'
import ListProductTypeEnum from '@/utils/listProductType'
import Modal from '@/components/common/modal.vue'
import editEmail from './components/myOrder/editEmail.vue'
import { maskEmail } from '@/utils/common'
const { t } = useI18n()
......
......@@ -17,6 +17,7 @@ export default defineConfig({
},
server: {
port: 8002,
host: '0.0.0.0',
},
css: {
preprocessorOptions: {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment