更新:增加“我的”相关功能
This commit is contained in:
7
App.vue
7
App.vue
@@ -12,8 +12,9 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style lang="scss">
|
||||||
@import "@/static/tailwind.css";
|
@import "@/style/tailwind.css";
|
||||||
|
@import "@/style/ui.scss";
|
||||||
/* 覆盖 Tailwind 的默认 block 样式 */
|
/* 覆盖 Tailwind 的默认 block 样式 */
|
||||||
img, svg, video, canvas, audio, iframe, embed, object {
|
img, svg, video, canvas, audio, iframe, embed, object {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -23,6 +24,6 @@ img, svg, video, canvas, audio, iframe, embed, object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
margin-bottom: 15px;
|
line-height: inherit;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
217
api/modules/user.ts
Normal file
217
api/modules/user.ts
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
// api/modules/user.ts
|
||||||
|
import { mainClient } from '@/api/clients/main'
|
||||||
|
import type { IApiResponse } from '@/api/types'
|
||||||
|
import type {
|
||||||
|
IUserInfo,
|
||||||
|
IVipInfo,
|
||||||
|
IOrder,
|
||||||
|
IVipPackage,
|
||||||
|
ITransaction,
|
||||||
|
IFeedbackForm,
|
||||||
|
IPageData
|
||||||
|
} from '@/types/user'
|
||||||
|
import { SERVICE_MAP } from '@/api/config'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户信息
|
||||||
|
*/
|
||||||
|
export async function getUserInfo() {
|
||||||
|
const res = await mainClient.request<IApiResponse<{ user: IUserInfo }>>({
|
||||||
|
url: 'common/user/getUserInfo',
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取VIP信息
|
||||||
|
*/
|
||||||
|
export async function getVipInfo() {
|
||||||
|
const res = await mainClient.request<IApiResponse<{ vipInfo: IVipInfo }>>({
|
||||||
|
url: 'bookAbroad/home/getVipInfo',
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订单列表
|
||||||
|
* @param current 当前页码
|
||||||
|
* @param limit 每页数量
|
||||||
|
*/
|
||||||
|
export async function getOrderList(current: number, limit: number) {
|
||||||
|
const res = await mainClient.request<IApiResponse<{ orders: IPageData<IOrder> }>>({
|
||||||
|
url: 'bookAbroad/home/getAbroadOrderList',
|
||||||
|
method: 'POST',
|
||||||
|
data: { current, limit }
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取VIP套餐列表
|
||||||
|
*/
|
||||||
|
export async function getVipPackages() {
|
||||||
|
const res = await mainClient.request<IApiResponse<{ sysDictDatas: IVipPackage[] }>>({
|
||||||
|
url: 'book/sysdictdata/getData',
|
||||||
|
method: 'POST',
|
||||||
|
data: { dictLabel: 'googleVip' }
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建订单
|
||||||
|
* @param data 订单数据
|
||||||
|
*/
|
||||||
|
export async function createOrder(data: any) {
|
||||||
|
const res = await mainClient.request<IApiResponse<{ orderSn: string }>>({
|
||||||
|
url: 'bookAbroad/order/placeOrder',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取交易记录列表
|
||||||
|
* @param userId 用户ID
|
||||||
|
*/
|
||||||
|
export async function getTransactionList(userId: number) {
|
||||||
|
const res = await mainClient.request<IApiResponse<{ transactionDetailsList: ITransaction[] }>>({
|
||||||
|
url: 'common/transactionDetails/getTransactionDetailsList',
|
||||||
|
method: 'POST',
|
||||||
|
data: { userId }
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户基本信息
|
||||||
|
* @param data 用户信息
|
||||||
|
*/
|
||||||
|
export async function updateUserInfo(data: Partial<IUserInfo>) {
|
||||||
|
const res = await mainClient.request<IApiResponse>({
|
||||||
|
url: 'book/user/update',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户邮箱
|
||||||
|
* @param id 用户ID
|
||||||
|
* @param email 邮箱地址
|
||||||
|
* @param code 验证码
|
||||||
|
*/
|
||||||
|
export async function updateEmail(id: number, email: string, code: string) {
|
||||||
|
const res = await mainClient.request<IApiResponse>({
|
||||||
|
url: 'common/user/updateUserEmail',
|
||||||
|
method: 'POST',
|
||||||
|
data: { id, email, code }
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户密码
|
||||||
|
* @param id 用户ID
|
||||||
|
* @param password 新密码
|
||||||
|
*/
|
||||||
|
export async function updatePassword(id: number, password: string) {
|
||||||
|
const res = await mainClient.request<IApiResponse>({
|
||||||
|
url: 'common/user/setPasswordById',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
data: { id, password }
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮箱验证码
|
||||||
|
* @param email 邮箱地址
|
||||||
|
*/
|
||||||
|
export async function sendEmailCode(email: string) {
|
||||||
|
const res = await mainClient.request<IApiResponse>({
|
||||||
|
url: 'common/user/getMailCaptcha',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
data: { email }
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传图片
|
||||||
|
* @param filePath 本地文件路径
|
||||||
|
* @returns 图片URL
|
||||||
|
*/
|
||||||
|
export function uploadImage(filePath: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.uploadFile({
|
||||||
|
url: `${SERVICE_MAP.MAIN}oss/fileoss`,
|
||||||
|
filePath,
|
||||||
|
name: 'file',
|
||||||
|
success: (res) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(res.data)
|
||||||
|
if (data.url) {
|
||||||
|
resolve(data.url)
|
||||||
|
} else {
|
||||||
|
reject(new Error('上传失败'))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交问题反馈
|
||||||
|
* @param data 反馈表单数据
|
||||||
|
*/
|
||||||
|
export async function submitFeedback(data: IFeedbackForm) {
|
||||||
|
const res = await mainClient.request<IApiResponse>({
|
||||||
|
url: 'common/sysFeedback/addSysFeedback',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证Google支付
|
||||||
|
* @param purchaseToken 购买令牌
|
||||||
|
* @param orderSn 订单号
|
||||||
|
* @param productId 产品ID
|
||||||
|
*/
|
||||||
|
export async function verifyGooglePay(purchaseToken: string, orderSn: string, productId: string) {
|
||||||
|
const res = await mainClient.request<IApiResponse>({
|
||||||
|
url: 'pay/googlepay/googleVerify',
|
||||||
|
method: 'POST',
|
||||||
|
data: { purchaseToken, orderSn, productId }
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证IAP支付
|
||||||
|
* @param data 支付数据
|
||||||
|
*/
|
||||||
|
export async function verifyIAP(data: any) {
|
||||||
|
const res = await mainClient.request<IApiResponse>({
|
||||||
|
url: 'Ipa/veri',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
40
components/nav-bar/nav-bar.vue
Normal file
40
components/nav-bar/nav-bar.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<view class="navbar">
|
||||||
|
<view class="statusBar" :style="{height:getStatusBarHeight()+'px'}"></view>
|
||||||
|
<view class="titleBar" :style="{height:getTitleBarHeight()+'px'}">
|
||||||
|
<wd-navbar :title="title" :left-arrow="leftArrow" @click="handleClickLeft"></wd-navbar>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view :style="{height:getNavBarHeight() +'px'}"></view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { getStatusBarHeight, getTitleBarHeight, getNavBarHeight, getLeftIconLeft} from "@/utils/system"
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
title:{
|
||||||
|
type:String,
|
||||||
|
default:""
|
||||||
|
},
|
||||||
|
leftArrow:{
|
||||||
|
type:Boolean,
|
||||||
|
default:true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClickLeft = () => {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.navbar{
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--wot-navbar-background);
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
115
locale/en.json
115
locale/en.json
@@ -73,5 +73,120 @@
|
|||||||
"passwordStrengthMedium": "Medium password strength.",
|
"passwordStrengthMedium": "Medium password strength.",
|
||||||
"passwordStrengthWeak": "please use a password consisting of at least two types: uppercase and lowercase letters, numbers, and symbols, with a length of 8 characters.",
|
"passwordStrengthWeak": "please use a password consisting of at least two types: uppercase and lowercase letters, numbers, and symbols, with a length of 8 characters.",
|
||||||
"passwordChanged": "Password changed successfully"
|
"passwordChanged": "Password changed successfully"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"submit": "Submit",
|
||||||
|
"save": "Save",
|
||||||
|
"delete": "Delete",
|
||||||
|
"edit": "Edit",
|
||||||
|
"back": "Back",
|
||||||
|
"loadMore": "Load More",
|
||||||
|
"noMore": "No More Data",
|
||||||
|
"noData": "No Data",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"success": "Success",
|
||||||
|
"failed": "Failed",
|
||||||
|
"networkError": "Network Error",
|
||||||
|
"pleaseInput": "Please Input",
|
||||||
|
"pleaseSelect": "Please Select"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"title": "My",
|
||||||
|
"myOrders": "My Orders",
|
||||||
|
"myBooklist": "My Booklist",
|
||||||
|
"profile": "Profile",
|
||||||
|
"about": "About Us",
|
||||||
|
"feedback": "Feedback",
|
||||||
|
"settings": "Settings",
|
||||||
|
"subscribe": "Subscribe",
|
||||||
|
"myAccount": "My Account",
|
||||||
|
"notSet": "Not Set",
|
||||||
|
"clickToSet": "Set",
|
||||||
|
"vip": "VIP Member",
|
||||||
|
"vipExpireTime": "Expire Time",
|
||||||
|
"nickname": "Nickname",
|
||||||
|
"name": "Name",
|
||||||
|
"email": "Email",
|
||||||
|
"phone": "Phone",
|
||||||
|
"age": "Age",
|
||||||
|
"sex": "Gender",
|
||||||
|
"male": "Male",
|
||||||
|
"female": "Female",
|
||||||
|
"avatar": "Avatar",
|
||||||
|
"changeAvatar": "Change Avatar",
|
||||||
|
"setAvatar": "Set Avatar",
|
||||||
|
"updateSuccess": "Update Success",
|
||||||
|
"updateFailed": "Update Failed",
|
||||||
|
"paymentMethod": "Payment Method",
|
||||||
|
"googlePay": "Google Pay",
|
||||||
|
"applePay": "Apple Pay",
|
||||||
|
"agreeText": "I have agreed",
|
||||||
|
"agreement": "Recharge Agreement",
|
||||||
|
"agreeFirst": "Please agree to the recharge agreement first",
|
||||||
|
"orderSn": "Order Number",
|
||||||
|
"createTime": "Create Time",
|
||||||
|
"amount": "Amount",
|
||||||
|
"paymentSuccess": "Payment Success",
|
||||||
|
"paymentFailed": "Payment Failed",
|
||||||
|
"paymentCanceled": "Payment Canceled",
|
||||||
|
"transactionType": "Transaction Type",
|
||||||
|
"recharge": "Recharge",
|
||||||
|
"consume": "Consume",
|
||||||
|
"remark": "Remark",
|
||||||
|
"hotline": "Hotline",
|
||||||
|
"customerEmail": "Customer Email",
|
||||||
|
"wechat": "WeChat",
|
||||||
|
"wechatTip": "Click on the image and long press to save it to your phone, or use WeChat to scan the QR code to add the customer service official WeChat.",
|
||||||
|
"checkVersion": "Check Version",
|
||||||
|
"version": "Version",
|
||||||
|
"logoff": "Delete Account",
|
||||||
|
"logout": "Logout",
|
||||||
|
"logoffConfirm": "Are you sure you want to delete your account? This action cannot be undone",
|
||||||
|
"logoffConfirmAgain": "The cancellation application has been submitted successfully, please contact customer service for follow-up operations: 022-24142321",
|
||||||
|
"logoutConfirm": "Are you sure you want to logout?",
|
||||||
|
"logoffSuccess": "Account Deleted",
|
||||||
|
"logoutSuccess": "Logout Success",
|
||||||
|
"copySuccess": "Copy Success",
|
||||||
|
"privacyPolicy": "Privacy Policy",
|
||||||
|
"appDescription": "An online ebook app offering a wide range of content, including medical literature, classical Chinese studies, literature, and ancient traditional Chinese medicine texts. It features 3D simulated page-flipping, eye-protection mode, and other advanced reading technologies for a comfortable reading experience. The app supports mixed text and image layouts, along with AI-powered voice narration for reading and listening. Some ebooks also have corresponding physical editions, providing users with more reading options.",
|
||||||
|
"issueType": "Issue Type",
|
||||||
|
"account": "Account",
|
||||||
|
"description": "Description",
|
||||||
|
"contactPhone": "Contact Phone",
|
||||||
|
"screenshots": "Screenshots",
|
||||||
|
"issueTypeAccount": "Account Issue",
|
||||||
|
"issueTypePayment": "Payment Issue",
|
||||||
|
"issueTypeOrder": "Order Related",
|
||||||
|
"issueTypeContent": "Content Issue",
|
||||||
|
"issueTypeSuggestion": "Feature Suggestion",
|
||||||
|
"issueTypeBug": "Bug Report",
|
||||||
|
"issueTypeOther": "Other",
|
||||||
|
"feedbackSuccess": "Feedback Submitted",
|
||||||
|
"feedbackFailed": "Feedback Failed",
|
||||||
|
"pleaseInputDescription": "Please enter description",
|
||||||
|
"pleaseInputPhone": "Please enter contact phone",
|
||||||
|
"pleaseInputOrderSn": "Please enter order number",
|
||||||
|
"uploadImageFailed": "Image upload failed",
|
||||||
|
"maxImagesCount": "Maximum 4 images",
|
||||||
|
"permissionDenied": "Permission Denied",
|
||||||
|
"cameraPermission": "Camera permission required",
|
||||||
|
"storagePermission": "Storage permission required",
|
||||||
|
"phonePermission": "Phone permission required",
|
||||||
|
"sendCodeSuccess": "Verification code sent",
|
||||||
|
"sendCodeFailed": "Failed to send code",
|
||||||
|
"countdown": "s to resend",
|
||||||
|
"emailFormat": "Please enter valid email",
|
||||||
|
"passwordFormat": "Invalid password format",
|
||||||
|
"passwordNotMatch": "Passwords do not match",
|
||||||
|
"pleaseInputCode": "Please enter verification code",
|
||||||
|
"pleaseInputPassword": "Please enter password",
|
||||||
|
"pleaseInputPasswordAgain": "Please enter password again",
|
||||||
|
"monthCard": "Monthly",
|
||||||
|
"seasonCard": "Quarterly",
|
||||||
|
"yearCard": "Yearly",
|
||||||
|
"days": "days",
|
||||||
|
"selectPackage": "Please select a package"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,5 +74,120 @@
|
|||||||
"passwordStrengthMedium": "密码强度中等",
|
"passwordStrengthMedium": "密码强度中等",
|
||||||
"passwordStrengthWeak": "请使用至少包含大小写字母、数字、符号中的两种类型,长度为8个字符的密码",
|
"passwordStrengthWeak": "请使用至少包含大小写字母、数字、符号中的两种类型,长度为8个字符的密码",
|
||||||
"passwordChanged": "密码修改成功"
|
"passwordChanged": "密码修改成功"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"confirm": "确认",
|
||||||
|
"cancel": "取消",
|
||||||
|
"submit": "提交",
|
||||||
|
"save": "保存",
|
||||||
|
"delete": "删除",
|
||||||
|
"edit": "编辑",
|
||||||
|
"back": "返回",
|
||||||
|
"loadMore": "加载更多",
|
||||||
|
"noMore": "没有更多数据",
|
||||||
|
"noData": "暂无数据",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"success": "操作成功",
|
||||||
|
"failed": "操作失败",
|
||||||
|
"networkError": "网络连接失败",
|
||||||
|
"pleaseInput": "请输入",
|
||||||
|
"pleaseSelect": "请选择"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"title": "我的",
|
||||||
|
"myOrders": "我的订单",
|
||||||
|
"myBooklist": "我的书单",
|
||||||
|
"profile": "个人资料",
|
||||||
|
"about": "关于我们",
|
||||||
|
"feedback": "问题反馈",
|
||||||
|
"settings": "设置",
|
||||||
|
"subscribe": "订阅",
|
||||||
|
"myAccount": "我的账户",
|
||||||
|
"notSet": "未设置",
|
||||||
|
"clickToSet": "设置",
|
||||||
|
"vip": "VIP会员",
|
||||||
|
"vipExpireTime": "到期时间",
|
||||||
|
"nickname": "昵称",
|
||||||
|
"name": "姓名",
|
||||||
|
"email": "邮箱",
|
||||||
|
"phone": "手机号",
|
||||||
|
"age": "年龄",
|
||||||
|
"sex": "性别",
|
||||||
|
"male": "男",
|
||||||
|
"female": "女",
|
||||||
|
"avatar": "头像",
|
||||||
|
"changeAvatar": "更换头像",
|
||||||
|
"setAvatar": "设置头像",
|
||||||
|
"updateSuccess": "更新成功",
|
||||||
|
"updateFailed": "更新失败",
|
||||||
|
"paymentMethod": "支付方式",
|
||||||
|
"googlePay": "Google Pay",
|
||||||
|
"applePay": "Apple Pay",
|
||||||
|
"agreeText": "我已同意",
|
||||||
|
"agreement": "充值协议",
|
||||||
|
"agreeFirst": "请先同意充值协议",
|
||||||
|
"orderSn": "订单编号",
|
||||||
|
"createTime": "创建时间",
|
||||||
|
"amount": "金额",
|
||||||
|
"paymentSuccess": "支付成功",
|
||||||
|
"paymentFailed": "支付失败",
|
||||||
|
"paymentCanceled": "支付已取消",
|
||||||
|
"transactionType": "交易类型",
|
||||||
|
"recharge": "充值",
|
||||||
|
"consume": "消费",
|
||||||
|
"remark": "备注",
|
||||||
|
"hotline": "客服热线",
|
||||||
|
"customerEmail": "客服邮箱",
|
||||||
|
"wechat": "微信号",
|
||||||
|
"wechatTip": "点击图片并长按保存到手机,或使用微信扫描二维码添加客服官方微信。",
|
||||||
|
"checkVersion": "检查版本",
|
||||||
|
"version": "版本",
|
||||||
|
"logoff": "注销账户",
|
||||||
|
"logout": "退出登录",
|
||||||
|
"logoffConfirm": "确定要注销账户吗?此操作不可恢复",
|
||||||
|
"logoffConfirmAgain": "注销申请已提交成功,请联系客服进行后续操作:022-24142321",
|
||||||
|
"logoutConfirm": "确定要退出登录吗?",
|
||||||
|
"logoffSuccess": "注销成功",
|
||||||
|
"logoutSuccess": "退出成功",
|
||||||
|
"copySuccess": "复制成功",
|
||||||
|
"privacyPolicy": "隐私政策",
|
||||||
|
"appDescription": "一款线上电子书APP,包含医学类、国学类、文学类、中医古籍等各种类型。3D仿真翻页、护眼模式等阅读技术,打造舒适阅读体验。图文混排,AI人声读书听书。部分电子书也有对应的纸质书,给予用户更多的阅读选择。",
|
||||||
|
"issueType": "问题类型",
|
||||||
|
"account": "账号",
|
||||||
|
"description": "问题描述",
|
||||||
|
"contactPhone": "联系电话",
|
||||||
|
"screenshots": "截图",
|
||||||
|
"issueTypeAccount": "账号问题",
|
||||||
|
"issueTypePayment": "支付问题",
|
||||||
|
"issueTypeOrder": "订单相关",
|
||||||
|
"issueTypeContent": "内容问题",
|
||||||
|
"issueTypeSuggestion": "功能建议",
|
||||||
|
"issueTypeBug": "BUG反馈",
|
||||||
|
"issueTypeOther": "其他",
|
||||||
|
"feedbackSuccess": "反馈提交成功",
|
||||||
|
"feedbackFailed": "反馈提交失败",
|
||||||
|
"pleaseInputDescription": "请输入问题描述",
|
||||||
|
"pleaseInputPhone": "请输入联系电话",
|
||||||
|
"pleaseInputOrderSn": "请输入订单编号",
|
||||||
|
"uploadImageFailed": "图片上传失败",
|
||||||
|
"maxImagesCount": "最多上传4张图片",
|
||||||
|
"permissionDenied": "权限被拒绝",
|
||||||
|
"cameraPermission": "需要相机权限",
|
||||||
|
"storagePermission": "需要存储权限",
|
||||||
|
"phonePermission": "需要电话权限",
|
||||||
|
"sendCodeSuccess": "验证码发送成功",
|
||||||
|
"sendCodeFailed": "验证码发送失败",
|
||||||
|
"countdown": "秒后重新发送",
|
||||||
|
"emailFormat": "请输入正确的邮箱格式",
|
||||||
|
"passwordFormat": "密码格式不正确",
|
||||||
|
"passwordNotMatch": "两次密码不一致",
|
||||||
|
"pleaseInputCode": "请输入验证码",
|
||||||
|
"pleaseInputPassword": "请输入密码",
|
||||||
|
"pleaseInputPasswordAgain": "请再次输入密码",
|
||||||
|
"monthCard": "月卡",
|
||||||
|
"seasonCard": "季卡",
|
||||||
|
"yearCard": "年卡",
|
||||||
|
"days": "天",
|
||||||
|
"selectPackage": "请选择套餐"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
50
pages.json
50
pages.json
@@ -25,6 +25,54 @@
|
|||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black"
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
"path": "pages/user/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "%user.title%",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"path": "pages/user/order/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "%user.myOrders%",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"path": "pages/user/wallet/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "%user.subscribe%",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"path": "pages/user/wallet/list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "%user.myAccount%",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"path": "pages/user/profile/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "%user.profile%",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"path": "pages/user/settings/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "%user.settings%",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"path": "pages/user/about/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "%user.about%",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"path": "pages/user/feedback/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "%user.feedback%",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
// "tabBar": {
|
// "tabBar": {
|
||||||
@@ -61,7 +109,7 @@
|
|||||||
"text": "%tabar.book%"
|
"text": "%tabar.book%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pagePath": "pages/my/my",
|
"pagePath": "pages/user/index",
|
||||||
"iconPath": "static/tab/icon4_n.png",
|
"iconPath": "static/tab/icon4_n.png",
|
||||||
"selectedIconPath": "static/tab/icon4_y.png",
|
"selectedIconPath": "static/tab/icon4_y.png",
|
||||||
"text": "%tabar.user%"
|
"text": "%tabar.user%"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="container">
|
<view class="container">
|
||||||
<view class="title bg-[red] text-left text-[#fff]">这是一个等待开发的首页</view>
|
<view class="title bg-[red] text-center text-[#fff]">这是一个等待开发的首页</view>
|
||||||
<view class="description bg-[red]">首页的内容是在线课程</view>
|
<view class="description bg-[red]">首页的内容是在线课程</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -99,9 +99,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="loginHelp" v-if="submitClickNum > 0">
|
<!-- <view class="loginHelp" v-if="submitClickNum > 0">
|
||||||
<text>登录遇到问题?</text><text class="link" @click="onPageJump('/pages/user/workOrder?name=login')">去反馈</text>
|
<text>登录遇到问题?</text><text class="link" @click="onPageJump('/pages/user/feedback?name=login')">去反馈</text>
|
||||||
</view>
|
</view> -->
|
||||||
|
|
||||||
<!-- 切换登录方式 -->
|
<!-- 切换登录方式 -->
|
||||||
<view class="qie-huan">
|
<view class="qie-huan">
|
||||||
@@ -240,7 +240,6 @@ const isCodeEmpty = () => {
|
|||||||
}
|
}
|
||||||
// 邮箱格式验证
|
// 邮箱格式验证
|
||||||
const isEmailVerified = (email: string) => {
|
const isEmailVerified = (email: string) => {
|
||||||
console.log(email, validateEmail(email))
|
|
||||||
if (!validateEmail(email)) {
|
if (!validateEmail(email)) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('login.emailError'),
|
title: t('login.emailError'),
|
||||||
|
|||||||
239
pages/user/about/index.vue
Normal file
239
pages/user/about/index.vue
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
<template>
|
||||||
|
<view class="about-page">
|
||||||
|
<!-- 自定义导航栏 -->
|
||||||
|
<nav-bar :title="$t('user.about')"></nav-bar>
|
||||||
|
|
||||||
|
<view class="about-content">
|
||||||
|
<!-- 应用信息 -->
|
||||||
|
<view class="app-info">
|
||||||
|
<image
|
||||||
|
src="/static/icon/home_icon_logo.jpg"
|
||||||
|
mode="aspectFit"
|
||||||
|
class="app-logo"
|
||||||
|
/>
|
||||||
|
<text v-if="appVersion" class="app-version">
|
||||||
|
{{ $t('user.version') }} {{ appVersion }}
|
||||||
|
</text>
|
||||||
|
<!-- 应用简介 -->
|
||||||
|
<view class="app-description">
|
||||||
|
<text>{{ $t('user.appDescription') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 联系信息 -->
|
||||||
|
<!-- <view class="contact-info">
|
||||||
|
<view class="contact-item">
|
||||||
|
<text class="label">{{ $t('user.hotline') }}</text>
|
||||||
|
<view class="value-wrapper">
|
||||||
|
<text class="value">022-24142321</text>
|
||||||
|
<image
|
||||||
|
src="/static/icon/tel.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
class="tel-icon"
|
||||||
|
@click="makePhoneCall"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view> -->
|
||||||
|
<wd-cell-group border class="contact-info">
|
||||||
|
<wd-cell :title="$t('user.hotline')" clickable @click="handlePhoneCall">
|
||||||
|
022-24142321
|
||||||
|
<img src="/static/icon/tel.png" width="23" height="23" />
|
||||||
|
</wd-cell>
|
||||||
|
<!-- <wd-cell :title="$t('user.wechat')" value="yilujiankangkefu" clickable @click="handlePhoneCall" /> -->
|
||||||
|
</wd-cell-group>
|
||||||
|
|
||||||
|
<!-- 隐私政策链接 -->
|
||||||
|
<view class="privacy-link">
|
||||||
|
<text class="link-text" @click="openPrivacyPolicy">
|
||||||
|
{{ $t('user.privacyPolicy') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { makePhoneCall } from '@/utils/index'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 导航栏高度
|
||||||
|
const statusBarHeight = ref(0)
|
||||||
|
const navbarHeight = ref('44px')
|
||||||
|
|
||||||
|
// 应用版本信息
|
||||||
|
const appVersion = ref('')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取应用版本信息
|
||||||
|
*/
|
||||||
|
const getAppVersion = () => {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
plus.runtime.getProperty(plus.runtime.appid, (info: any) => {
|
||||||
|
appVersion.value = `${info.name} ${info.version}`
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef APP-PLUS
|
||||||
|
appVersion.value = '1.0.0'
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拨打电话
|
||||||
|
*/
|
||||||
|
const handlePhoneCall = () => {
|
||||||
|
makePhoneCall('022-24142321', t('user.hotline'), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开隐私政策
|
||||||
|
*/
|
||||||
|
const openPrivacyPolicy = () => {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
plus.runtime.openURL('https://www.amazinglimited.com/privacy.html')
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef APP-PLUS
|
||||||
|
uni.showToast({
|
||||||
|
title: '请在APP中查看',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getAppVersion()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$theme-color: #54a966;
|
||||||
|
|
||||||
|
.about-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f7faf9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-navbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1rpx solid #e5e5e5;
|
||||||
|
|
||||||
|
.navbar-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 44px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.navbar-left {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-content {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-info {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
padding: 60rpx 40rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.app-logo {
|
||||||
|
width: 150rpx;
|
||||||
|
height: 150rpx;
|
||||||
|
margin: 0 auto 20rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-version {
|
||||||
|
display: block;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-info {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 30rpx;
|
||||||
|
border-top: 1rpx solid #e5e5e5;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tel-icon {
|
||||||
|
width: 46rpx;
|
||||||
|
height: 46rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-description {
|
||||||
|
padding: 30rpx 30rpx 0;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 40rpx;
|
||||||
|
text-indent: 2em;
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-link {
|
||||||
|
text-align: right;
|
||||||
|
padding: 30rpx;
|
||||||
|
|
||||||
|
.link-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: $theme-color;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
363
pages/user/feedback/index.vue
Normal file
363
pages/user/feedback/index.vue
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
<template>
|
||||||
|
<view class="feedback-page">
|
||||||
|
<!-- 自定义导航栏 -->
|
||||||
|
<nav-bar :title="$t('user.feedback')"></nav-bar>
|
||||||
|
|
||||||
|
<view class="feedback-content">
|
||||||
|
<view class="feedback-form">
|
||||||
|
<!-- 问题类型 -->
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label required">{{ $t('user.issueType') }}</text>
|
||||||
|
<wd-select-picker
|
||||||
|
v-model="form.type"
|
||||||
|
type="radio"
|
||||||
|
:columns="issueTypeOptions"
|
||||||
|
:placeholder="$t('common.pleaseSelect')"
|
||||||
|
:show-confirm="false"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 账号 -->
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label required">{{ $t('user.account') }}</text>
|
||||||
|
<wd-input
|
||||||
|
v-model="form.account"
|
||||||
|
:placeholder="$t('user.account')"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 订单编号(条件显示) -->
|
||||||
|
<view v-if="form.type === '3'" class="form-item">
|
||||||
|
<text class="label required">{{ $t('user.orderSn') }}</text>
|
||||||
|
<wd-input
|
||||||
|
v-model="form.relation"
|
||||||
|
type="number"
|
||||||
|
:placeholder="$t('user.pleaseInputOrderSn')"
|
||||||
|
@input="handleRelationInput"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 问题描述 -->
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label required">{{ $t('user.description') }}</text>
|
||||||
|
<wd-textarea
|
||||||
|
v-model="form.content"
|
||||||
|
auto-height
|
||||||
|
:placeholder="$t('user.pleaseInputDescription')"
|
||||||
|
:maxlength="200"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 联系电话 -->
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label required">{{ $t('user.contactPhone') }}</text>
|
||||||
|
<wd-input
|
||||||
|
v-model="form.contactInformation"
|
||||||
|
type="number"
|
||||||
|
:placeholder="$t('user.pleaseInputPhone')"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 截图上传 -->
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label">{{ $t('user.screenshots') }}</text>
|
||||||
|
<wd-upload
|
||||||
|
:fileList="imageList"
|
||||||
|
:limit="4"
|
||||||
|
action="https://global.nuttyreading.com/oss/fileoss"
|
||||||
|
@change="handleChangeImage"
|
||||||
|
/>
|
||||||
|
<text class="tip-text">{{ $t('user.maxImagesCount') }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 提交按钮 -->
|
||||||
|
<view class="submit-button">
|
||||||
|
<wd-button
|
||||||
|
type="success"
|
||||||
|
block
|
||||||
|
@click="handleSubmit"
|
||||||
|
>
|
||||||
|
{{ $t('common.submit') }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { submitFeedback } from '@/api/modules/user'
|
||||||
|
import type { IFeedbackForm } from '@/types/user'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 问题类型选项
|
||||||
|
const issueTypeOptions = computed(() => [
|
||||||
|
{ label: t('user.issueTypeAccount'), value: '1' },
|
||||||
|
{ label: t('user.issueTypePayment'), value: '2' },
|
||||||
|
{ label: t('user.issueTypeOrder'), value: '3' },
|
||||||
|
{ label: t('user.issueTypeContent'), value: '4' },
|
||||||
|
{ label: t('user.issueTypeSuggestion'), value: '5' },
|
||||||
|
{ label: t('user.issueTypeBug'), value: '6' },
|
||||||
|
{ label: t('user.issueTypeOther'), value: '7' }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = ref<IFeedbackForm>({
|
||||||
|
type: '',
|
||||||
|
account: userStore.userInfo.email || userStore.userInfo.phone || '',
|
||||||
|
relation: '',
|
||||||
|
content: '',
|
||||||
|
contactInformation: '',
|
||||||
|
image: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 图片列表
|
||||||
|
const imageList = ref<any[]>([])
|
||||||
|
|
||||||
|
// 错误状态
|
||||||
|
const relationError = ref(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理订单编号输入
|
||||||
|
*/
|
||||||
|
const handleRelationInput = () => {
|
||||||
|
relationError.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传前检查
|
||||||
|
*/
|
||||||
|
const beforeUpload = async (file: any) => {
|
||||||
|
// TODO: 检查相机和存储权限
|
||||||
|
|
||||||
|
// 检查文件大小
|
||||||
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '图片大小不能超过5MB',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件变化
|
||||||
|
*/
|
||||||
|
const imageUrl = ref<any[]>([])
|
||||||
|
const handleChangeImage = ({ fileList }: any) => {
|
||||||
|
console.log('上传文件变化:', fileList)
|
||||||
|
imageUrl.value = []
|
||||||
|
fileList.forEach((file:any) => {
|
||||||
|
imageUrl.value.push(JSON.parse(file.response).url)
|
||||||
|
})
|
||||||
|
console.log('图片列表:', imageUrl.value)
|
||||||
|
// this.fileList = fileList
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单验证
|
||||||
|
*/
|
||||||
|
const validateForm = (): boolean => {
|
||||||
|
// 验证问题类型
|
||||||
|
if (!form.value.type) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('common.pleaseSelect') + t('user.issueType'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证账号
|
||||||
|
if (!form.value.account) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('user.pleaseInput') + t('user.account'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证订单编号(当类型为订单相关时)
|
||||||
|
if (form.value.type === '3') {
|
||||||
|
if (!form.value.relation) {
|
||||||
|
relationError.value = true
|
||||||
|
uni.showToast({
|
||||||
|
title: t('user.pleaseInputOrderSn'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证问题描述
|
||||||
|
if (!form.value.content) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('user.pleaseInputDescription'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证联系电话
|
||||||
|
if (!form.value.contactInformation) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('user.pleaseInputPhone'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交反馈
|
||||||
|
*/
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
// 表单验证
|
||||||
|
if (!validateForm()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
uni.showLoading()
|
||||||
|
|
||||||
|
// 处理图片
|
||||||
|
if (imageUrl.value.length > 0) {
|
||||||
|
form.value.image = imageUrl.value.join(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交反馈
|
||||||
|
await submitFeedback(form.value)
|
||||||
|
|
||||||
|
uni.hideLoading()
|
||||||
|
|
||||||
|
uni.showModal({
|
||||||
|
title: t('global.tips'),
|
||||||
|
content: t('user.feedbackSuccess'),
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: t('common.confirm'),
|
||||||
|
success: () => {
|
||||||
|
// 清空表单
|
||||||
|
imageList.value = []
|
||||||
|
imageUrl.value = []
|
||||||
|
form.value = {
|
||||||
|
type: '',
|
||||||
|
account: userStore.userInfo.email || userStore.userInfo.phone || '',
|
||||||
|
relation: '',
|
||||||
|
content: '',
|
||||||
|
contactInformation: '',
|
||||||
|
image: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交反馈失败:', error)
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: t('user.feedbackFailed'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$theme-color: #54a966;
|
||||||
|
|
||||||
|
.feedback-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f7faf9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-navbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1rpx solid #e5e5e5;
|
||||||
|
|
||||||
|
.navbar-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 44px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.navbar-left {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-content {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-form {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: block;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 15rpx;
|
||||||
|
|
||||||
|
&.required::before {
|
||||||
|
content: '*';
|
||||||
|
color: red;
|
||||||
|
margin-right: 5rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
display: block;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: red;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-text {
|
||||||
|
display: block;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button {
|
||||||
|
margin-top: 50rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
334
pages/user/index.vue
Normal file
334
pages/user/index.vue
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
<template>
|
||||||
|
<view class="user-page">
|
||||||
|
<!-- 设置图标 -->
|
||||||
|
<view class="settings-icon" @click="goSettings">
|
||||||
|
<wd-icon name="setting1" size="24px" color="#666" />
|
||||||
|
<text>{{ $t('user.settings') }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 用户信息区域 -->
|
||||||
|
<view class="user-info-section">
|
||||||
|
<view class="user-info">
|
||||||
|
<image
|
||||||
|
:src="userInfo.avatar || defaultAvatar"
|
||||||
|
class="avatar"
|
||||||
|
@click="goProfile"
|
||||||
|
/>
|
||||||
|
<view class="user-details">
|
||||||
|
<text class="nickname">{{ userInfo.nickname || $t('user.notSet') }}</text>
|
||||||
|
<text v-if="userInfo.email" class="email">{{ userInfo.email }}</text>
|
||||||
|
<text v-if="vipInfo.endTime && platform === 'ios'" class="vip-time">
|
||||||
|
VIP {{ vipInfo.endTime.split(' ')[0] }} {{ $t('user.vipExpireTime') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- VIP订阅卡片 -->
|
||||||
|
<view class="vip-card-section">
|
||||||
|
<view class="vip-card">
|
||||||
|
<view v-if="vipInfo.id" class="vip-info">
|
||||||
|
<text class="label">{{ $t('user.vip') }}</text>
|
||||||
|
<text class="value">{{ vipInfo.endTime ? vipInfo.endTime.split(' ')[0] : '' }}</text>
|
||||||
|
</view>
|
||||||
|
<wd-button
|
||||||
|
v-else
|
||||||
|
type="success"
|
||||||
|
size="small"
|
||||||
|
@click="goSubscribe"
|
||||||
|
>
|
||||||
|
{{ $t('user.subscribe') }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 功能菜单列表 -->
|
||||||
|
<view class="menu-section">
|
||||||
|
<view class="menu-list">
|
||||||
|
<view
|
||||||
|
v-for="item in menuItems"
|
||||||
|
:key="item.id"
|
||||||
|
class="menu-item"
|
||||||
|
@click="handleMenuClick(item)"
|
||||||
|
>
|
||||||
|
<text class="menu-text">{{ item.name }}</text>
|
||||||
|
<wd-icon name="arrow-right" size="16px" color="#aaa" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { getUserInfo, getVipInfo } from '@/api/modules/user'
|
||||||
|
import type { IVipInfo } from '@/types/user'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 默认头像
|
||||||
|
const defaultAvatar = '/static/home_icon.png'
|
||||||
|
|
||||||
|
// 用户信息
|
||||||
|
const userInfo = computed(() => userStore.userInfo)
|
||||||
|
|
||||||
|
// VIP信息
|
||||||
|
const vipInfo = ref<IVipInfo>({
|
||||||
|
id: 0,
|
||||||
|
endTime: '',
|
||||||
|
vipType: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 平台信息
|
||||||
|
const platform = ref('')
|
||||||
|
|
||||||
|
// 菜单项
|
||||||
|
const menuItems = computed(() => [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: t('user.myOrders'),
|
||||||
|
url: '/pages/user/order/index',
|
||||||
|
type: 'pageJump'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: t('user.myBooklist'),
|
||||||
|
url: '/pages/book/index',
|
||||||
|
type: 'switchTab'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: t('user.profile'),
|
||||||
|
url: '/pages/user/profile/index',
|
||||||
|
type: 'pageJump'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: t('user.about'),
|
||||||
|
url: '/pages/user/about/index',
|
||||||
|
type: 'pageJump'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: t('user.feedback'),
|
||||||
|
url: '/pages/user/feedback/index',
|
||||||
|
type: 'pageJump'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取平台信息
|
||||||
|
*/
|
||||||
|
const getPlatform = () => {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
platform.value = systemInfo.platform === 'android' ? 'android' : 'ios'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据
|
||||||
|
*/
|
||||||
|
const getData = async () => {
|
||||||
|
try {
|
||||||
|
// 获取用户信息
|
||||||
|
const userRes = await getUserInfo()
|
||||||
|
if (userRes.user) {
|
||||||
|
userStore.setUserInfo(userRes.user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取VIP信息
|
||||||
|
const vipRes = await getVipInfo()
|
||||||
|
if (vipRes.vipInfo) {
|
||||||
|
vipInfo.value = vipRes.vipInfo
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到设置页面
|
||||||
|
*/
|
||||||
|
const goSettings = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/user/settings/index'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到个人资料页面
|
||||||
|
*/
|
||||||
|
const goProfile = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/user/profile/index'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到订阅页面
|
||||||
|
*/
|
||||||
|
const goSubscribe = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/user/wallet/index'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理菜单点击
|
||||||
|
*/
|
||||||
|
const handleMenuClick = (item: any) => {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'pageJump':
|
||||||
|
uni.navigateTo({
|
||||||
|
url: item.url
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'switchTab':
|
||||||
|
uni.switchTab({
|
||||||
|
url: item.url
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getPlatform()
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$theme-color: #54a966;
|
||||||
|
|
||||||
|
.user-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f7faf9;
|
||||||
|
padding-top: 130rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 40rpx;
|
||||||
|
top: 60rpx;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
|
||||||
|
text {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-section {
|
||||||
|
padding: 0 30rpx;
|
||||||
|
margin-bottom: 50rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
border-radius: 100rpx;
|
||||||
|
margin-right: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-details {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.nickname {
|
||||||
|
display: block;
|
||||||
|
font-size: 38rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vip-time {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vip-card-section {
|
||||||
|
padding: 0 20rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vip-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
padding: 40rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.vip-info {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: block;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
display: block;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: $theme-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-section {
|
||||||
|
padding: 20rpx 20rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-list {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 30rpx;
|
||||||
|
border-bottom: 1rpx solid #e0e0e0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
line-height: 40rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
309
pages/user/order/index.vue
Normal file
309
pages/user/order/index.vue
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
<template>
|
||||||
|
<view class="order-page">
|
||||||
|
<!-- 自定义导航栏 -->
|
||||||
|
<view class="custom-navbar" :style="{ height: navbarHeight }">
|
||||||
|
<view class="navbar-content" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
|
<view class="navbar-left" @click="goBack">
|
||||||
|
<wd-icon name="arrow-left" size="18px" color="#333" />
|
||||||
|
</view>
|
||||||
|
<text class="navbar-title">{{ $t('user.myOrders') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 订单列表 -->
|
||||||
|
<scroll-view
|
||||||
|
v-if="orderList.length > 0"
|
||||||
|
scroll-y
|
||||||
|
class="order-scroll"
|
||||||
|
:style="{ paddingTop: navbarHeight }"
|
||||||
|
@scrolltolower="loadMore"
|
||||||
|
>
|
||||||
|
<view class="order-list">
|
||||||
|
<view
|
||||||
|
v-for="order in orderList"
|
||||||
|
:key="order.id"
|
||||||
|
class="order-item"
|
||||||
|
>
|
||||||
|
<view class="order-header">
|
||||||
|
<text class="book-name">{{ order.bookEntity.name }}</text>
|
||||||
|
<view class="price-info">
|
||||||
|
<image
|
||||||
|
v-if="order.paymentMethod === '4'"
|
||||||
|
src="/static/icon/coin.png"
|
||||||
|
class="payment-icon"
|
||||||
|
/>
|
||||||
|
<image
|
||||||
|
v-else
|
||||||
|
src="/static/icon/currency.png"
|
||||||
|
class="payment-icon"
|
||||||
|
/>
|
||||||
|
<text class="price">{{ order.orderMoney }}</text>
|
||||||
|
<text v-if="order.paymentMethod === '5'" class="currency">NZD</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<text class="order-sn">
|
||||||
|
{{ $t('user.orderSn') }}
|
||||||
|
{{ order.orderSn }}
|
||||||
|
</text>
|
||||||
|
<text class="create-time">{{ order.createTime }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 加载提示 -->
|
||||||
|
<view class="load-tips">
|
||||||
|
<wd-divider v-if="hasMore && showLoadTip">
|
||||||
|
{{ $t('common.loadMore') }}
|
||||||
|
</wd-divider>
|
||||||
|
<wd-divider v-if="!hasMore && showLoadTip">
|
||||||
|
{{ $t('common.noMore') }}
|
||||||
|
</wd-divider>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<view v-else-if="!loading" class="empty-state">
|
||||||
|
<text class="empty-text">{{ $t('common.noData') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { getOrderList } from '@/api/modules/user'
|
||||||
|
import type { IOrder } from '@/types/user'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 导航栏高度
|
||||||
|
const statusBarHeight = ref(0)
|
||||||
|
const navbarHeight = ref('44px')
|
||||||
|
|
||||||
|
// 订单列表
|
||||||
|
const orderList = ref<IOrder[]>([])
|
||||||
|
|
||||||
|
// 分页信息
|
||||||
|
const page = ref({
|
||||||
|
current: 1,
|
||||||
|
limit: 10
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(false)
|
||||||
|
const hasMore = ref(true)
|
||||||
|
const showLoadTip = ref(false)
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导航栏高度
|
||||||
|
*/
|
||||||
|
const getNavbarHeight = () => {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
statusBarHeight.value = systemInfo.statusBarHeight || 0
|
||||||
|
|
||||||
|
let navBarHeight = 44
|
||||||
|
if (systemInfo.model.indexOf('iPhone') !== -1 && parseInt(systemInfo.model.slice(-2)) >= 11) {
|
||||||
|
navBarHeight = 48
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalHeight = statusBarHeight.value + navBarHeight
|
||||||
|
navbarHeight.value = totalHeight + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订单列表
|
||||||
|
*/
|
||||||
|
const getData = async () => {
|
||||||
|
if (loading.value || !hasMore.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
uni.showLoading({
|
||||||
|
title: t('common.loading')
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await getOrderList(page.value.current, page.value.limit)
|
||||||
|
|
||||||
|
uni.hideLoading()
|
||||||
|
|
||||||
|
if (res.orders && res.orders.records) {
|
||||||
|
total.value = res.orders.total
|
||||||
|
const records = res.orders.records
|
||||||
|
|
||||||
|
if (records.length > 0) {
|
||||||
|
orderList.value = [...orderList.value, ...records]
|
||||||
|
page.value.current += 1
|
||||||
|
showLoadTip.value = true
|
||||||
|
|
||||||
|
// 判断是否还有更多数据
|
||||||
|
if (orderList.value.length >= total.value || records.length < page.value.limit) {
|
||||||
|
hasMore.value = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasMore.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取订单列表失败:', error)
|
||||||
|
uni.hideLoading()
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载更多
|
||||||
|
*/
|
||||||
|
const loadMore = () => {
|
||||||
|
if (!loading.value && hasMore.value) {
|
||||||
|
getData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回上一页
|
||||||
|
*/
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getNavbarHeight()
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$theme-color: #54a966;
|
||||||
|
|
||||||
|
.order-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f7faf9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-navbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1rpx solid #e5e5e5;
|
||||||
|
|
||||||
|
.navbar-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 44px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.navbar-left {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-scroll {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-list {
|
||||||
|
padding: 0 20rpx 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-item {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.order-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
.book-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 34rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
max-width: 85%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.payment-icon {
|
||||||
|
width: 38rpx;
|
||||||
|
height: 38rpx;
|
||||||
|
margin-right: 5rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: $theme-color;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: $theme-color;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 5rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-sn {
|
||||||
|
display: block;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 38rpx;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-time {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-tips {
|
||||||
|
padding: 40rpx 0 20rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 60vh;
|
||||||
|
padding-top: 100rpx;
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
645
pages/user/profile/index.vue
Normal file
645
pages/user/profile/index.vue
Normal file
@@ -0,0 +1,645 @@
|
|||||||
|
<template>
|
||||||
|
<view class="profile-page">
|
||||||
|
<!-- 自定义导航栏 -->
|
||||||
|
<nav-bar :title="$t('user.profile')"></nav-bar>
|
||||||
|
|
||||||
|
<view class="profile-content">
|
||||||
|
<!-- 头像区域 -->
|
||||||
|
<view class="avatar-section">
|
||||||
|
<image
|
||||||
|
:src="userInfo.avatar || defaultAvatar"
|
||||||
|
class="avatar"
|
||||||
|
@click="editAvatar"
|
||||||
|
/>
|
||||||
|
<text class="avatar-text" @click="editAvatar">
|
||||||
|
{{ userInfo.avatar ? $t('user.changeAvatar') : $t('user.setAvatar') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 信息列表 -->
|
||||||
|
<wd-cell-group border custom-class="info-list">
|
||||||
|
<wd-cell
|
||||||
|
v-for="field in fields"
|
||||||
|
title-width="100rpx"
|
||||||
|
:key="field.key"
|
||||||
|
:title="field.label"
|
||||||
|
clickable
|
||||||
|
@click="editField(field)"
|
||||||
|
>
|
||||||
|
<view class="value-wrapper">
|
||||||
|
<text v-if="userInfo[field.key]" class="value">
|
||||||
|
{{ formatValue(field, userInfo[field.key]) }}
|
||||||
|
</text>
|
||||||
|
<text v-else class="placeholder">{{ $t('user.notSet') }}</text>
|
||||||
|
<wd-icon
|
||||||
|
v-if="userInfo[field.key]"
|
||||||
|
name="edit-1"
|
||||||
|
size="16px"
|
||||||
|
color="#54a966"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</wd-cell>
|
||||||
|
</wd-cell-group>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 编辑弹窗 -->
|
||||||
|
<wd-popup v-model="showEditModal" position="bottom">
|
||||||
|
<view class="edit-modal">
|
||||||
|
<text class="modal-title">{{ editTitle }}</text>
|
||||||
|
|
||||||
|
<!-- 昵称/姓名/年龄编辑 -->
|
||||||
|
<view v-if="['nickname', 'name', 'age'].includes(currentField?.key || '')">
|
||||||
|
<wd-input
|
||||||
|
v-model="editValue"
|
||||||
|
:placeholder="getPlaceholder(currentField?.key)"
|
||||||
|
:type="currentField?.key === 'age' ? 'number' : 'text'"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 邮箱编辑 -->
|
||||||
|
<view v-if="currentField?.key === 'email'">
|
||||||
|
<wd-input
|
||||||
|
v-model="editForm.email"
|
||||||
|
:placeholder="$t('user.email')"
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
|
<view class="code-input">
|
||||||
|
<wd-input
|
||||||
|
v-model="editForm.code"
|
||||||
|
:placeholder="$t('login.codePlaceholder')"
|
||||||
|
type="number"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
<wd-button
|
||||||
|
size="small"
|
||||||
|
:disabled="codeDisabled"
|
||||||
|
class="w-[100px]"
|
||||||
|
@click="sendCode"
|
||||||
|
>
|
||||||
|
{{ codeText }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 密码编辑 -->
|
||||||
|
<view v-if="currentField?.key === 'password'">
|
||||||
|
<wd-input
|
||||||
|
v-model="editForm.password"
|
||||||
|
:placeholder="$t('user.pleaseInputPassword')"
|
||||||
|
type="password"
|
||||||
|
@input="checkPasswordStrength"
|
||||||
|
/>
|
||||||
|
<view class="password-note">
|
||||||
|
<view v-if="passwordNote">{{ passwordNote }}</view>
|
||||||
|
<view v-html="passwordStrength"></view>
|
||||||
|
</view>
|
||||||
|
<wd-input
|
||||||
|
v-model="editForm.confirmPassword"
|
||||||
|
:placeholder="$t('user.pleaseInputPasswordAgain')"
|
||||||
|
type="password"
|
||||||
|
style="margin-top: 20rpx"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 性别编辑 -->
|
||||||
|
<view v-if="currentField?.key === 'sex'">
|
||||||
|
<wd-radio-group v-model="editValue" shape="button" class="text-center">
|
||||||
|
<wd-radio v-for="item in sexOptions" :key="item.value" :value="item.value">{{ item.label }}</wd-radio>
|
||||||
|
</wd-radio-group>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 头像上传 -->
|
||||||
|
<view v-if="currentField?.key === 'avatar'">
|
||||||
|
<wd-upload
|
||||||
|
:file-list="avatarList"
|
||||||
|
:limit="1"
|
||||||
|
action="https://global.nuttyreading.com/oss/fileoss"
|
||||||
|
@change="handleChangeAvatar"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 确认按钮 -->
|
||||||
|
<wd-button type="success" block :round="false" @click="handleSubmit" style="margin-top: 50rpx">
|
||||||
|
{{ $t('common.confirm') }}
|
||||||
|
</wd-button>
|
||||||
|
<view class="cancel-btn" @click="closeModal">
|
||||||
|
{{ $t('common.cancel') }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import {
|
||||||
|
getUserInfo,
|
||||||
|
updateUserInfo,
|
||||||
|
updateEmail,
|
||||||
|
updatePassword,
|
||||||
|
uploadImage,
|
||||||
|
sendEmailCode
|
||||||
|
} from '@/api/modules/user'
|
||||||
|
import { checkPasswordStrength as validatePassword } from '@/utils/validator'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 默认头像
|
||||||
|
const defaultAvatar = '/static/home_icon.png'
|
||||||
|
|
||||||
|
// 字段列表
|
||||||
|
const fields = computed(() => [
|
||||||
|
{ key: 'nickname', label: t('user.nickname') },
|
||||||
|
{ key: 'email', label: t('user.email') },
|
||||||
|
{ key: 'password', label: t('login.password') },
|
||||||
|
{ key: 'age', label: t('user.age') },
|
||||||
|
{ key: 'sex', label: t('user.sex') },
|
||||||
|
{ key: 'name', label: t('user.name') }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 性别选项
|
||||||
|
const sexOptions = [
|
||||||
|
{ label: t('user.male'), value: 1 },
|
||||||
|
{ label: t('user.female'), value: 2 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 编辑相关
|
||||||
|
const showEditModal = ref(false)
|
||||||
|
const currentField = ref<any>(null)
|
||||||
|
const editTitle = ref('')
|
||||||
|
const editValue = ref<any>('')
|
||||||
|
const editForm = ref({
|
||||||
|
email: '',
|
||||||
|
code: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 验证码相关
|
||||||
|
const codeDisabled = ref(false)
|
||||||
|
const codeText = ref(t('login.getCode'))
|
||||||
|
let codeTimer: any = null
|
||||||
|
|
||||||
|
// 密码强度
|
||||||
|
const passwordNote = ref('')
|
||||||
|
const passwordStrength = ref('')
|
||||||
|
const passwordOk = ref(false)
|
||||||
|
|
||||||
|
// 头像上传
|
||||||
|
const avatarList = ref<any[]>([])
|
||||||
|
const avatarUrl = ref('')
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户数据
|
||||||
|
*/
|
||||||
|
const userInfo = ref<any>({}) // 用户信息
|
||||||
|
const getData = async () => {
|
||||||
|
uni.showLoading()
|
||||||
|
try {
|
||||||
|
const res = await getUserInfo()
|
||||||
|
uni.hideLoading()
|
||||||
|
if (res.result) {
|
||||||
|
userStore.setUserInfo(res.result)
|
||||||
|
userInfo.value = res.result
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取用户信息失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化显示值
|
||||||
|
*/
|
||||||
|
const formatValue = (field: any, value: any) => {
|
||||||
|
if (field.key === 'sex') {
|
||||||
|
return value === 1 ? t('user.male') : t('user.female')
|
||||||
|
}
|
||||||
|
if (field.key === 'password') {
|
||||||
|
return '******'
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取输入框占位符
|
||||||
|
*/
|
||||||
|
const getPlaceholder = (key?: string) => {
|
||||||
|
const placeholders: Record<string, string> = {
|
||||||
|
nickname: t('common.pleaseInput') + t('user.nickname'),
|
||||||
|
name: t('common.pleaseInput') + t('user.name'),
|
||||||
|
age: t('common.pleaseInput') + t('user.age')
|
||||||
|
}
|
||||||
|
return placeholders[key || ''] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑字段
|
||||||
|
*/
|
||||||
|
const editField = (field: any) => {
|
||||||
|
currentField.value = field
|
||||||
|
editTitle.value = (userInfo.value[field.key] ? t('common.edit') : t('user.clickToSet')) + field.label
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
editValue.value = ''
|
||||||
|
editForm.value = {
|
||||||
|
email: '',
|
||||||
|
code: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
}
|
||||||
|
passwordNote.value = ''
|
||||||
|
passwordStrength.value = ''
|
||||||
|
|
||||||
|
// 设置初始值
|
||||||
|
if (field.key === 'sex' && userInfo.value.sex) {
|
||||||
|
editValue.value = userInfo.value.sex
|
||||||
|
}
|
||||||
|
|
||||||
|
showEditModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑头像
|
||||||
|
*/
|
||||||
|
const editAvatar = () => {
|
||||||
|
currentField.value = { key: 'avatar', label: t('user.avatar') }
|
||||||
|
editTitle.value = userInfo.value.avatar ? t('user.changeAvatar') : t('user.setAvatar')
|
||||||
|
avatarList.value = userInfo.value.avatar ? [{ url: userInfo.value.avatar }] : []
|
||||||
|
showEditModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传前检查
|
||||||
|
*/
|
||||||
|
const beforeUpload = async () => {
|
||||||
|
// TODO: 检查相机和存储权限
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理头像上传变化
|
||||||
|
*/
|
||||||
|
const handleChangeAvatar = async(filelist: any) => {
|
||||||
|
if (filelist.fileList.length > 0) {
|
||||||
|
avatarUrl.value = JSON.parse(filelist.fileList[0].response).url
|
||||||
|
} else {
|
||||||
|
avatarUrl.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除头像
|
||||||
|
*/
|
||||||
|
const handleDeleteAvatar = () => {
|
||||||
|
avatarList.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送验证码
|
||||||
|
*/
|
||||||
|
const sendCode = async () => {
|
||||||
|
if (!editForm.value.email) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('user.pleaseInput') + t('user.email'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendEmailCode(editForm.value.email)
|
||||||
|
uni.showToast({
|
||||||
|
title: t('user.sendCodeSuccess'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
startCountdown()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('发送验证码失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码倒计时
|
||||||
|
*/
|
||||||
|
const startCountdown = () => {
|
||||||
|
if (codeTimer) {
|
||||||
|
clearInterval(codeTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
codeDisabled.value = true
|
||||||
|
let countdown = 60
|
||||||
|
codeText.value = `${countdown}S`
|
||||||
|
|
||||||
|
codeTimer = setInterval(() => {
|
||||||
|
countdown--
|
||||||
|
codeText.value = `${countdown}S`
|
||||||
|
if (countdown <= 0) {
|
||||||
|
clearInterval(codeTimer)
|
||||||
|
codeText.value = t('login.getCode')
|
||||||
|
codeDisabled.value = false
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查密码强度
|
||||||
|
*/
|
||||||
|
const checkPasswordStrength = () => {
|
||||||
|
const password = editForm.value.password
|
||||||
|
if (!password) {
|
||||||
|
passwordNote.value = ''
|
||||||
|
passwordStrength.value = ''
|
||||||
|
passwordOk.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const strength = validatePassword(password)
|
||||||
|
|
||||||
|
if (strength === 'strong') {
|
||||||
|
passwordStrength.value = `<span style="color:#18bc37">${t('forget.passwordStrengthStrong')}</span>`
|
||||||
|
passwordNote.value = ''
|
||||||
|
passwordOk.value = true
|
||||||
|
} else if (strength === 'medium') {
|
||||||
|
passwordStrength.value = `<span style="color:#2979ff">${t('forget.passwordStrengthMedium')}</span>`
|
||||||
|
passwordNote.value = t('forget.passwordStrengthWeak')
|
||||||
|
passwordOk.value = true
|
||||||
|
} else if (strength === 'weak') {
|
||||||
|
passwordStrength.value = `<span style="color:#f3a73f">弱</span>`
|
||||||
|
passwordNote.value = t('forget.passwordStrengthWeak')
|
||||||
|
passwordOk.value = false
|
||||||
|
} else {
|
||||||
|
passwordStrength.value = ''
|
||||||
|
passwordNote.value = t('forget.passwordStrengthWeak')
|
||||||
|
passwordOk.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交修改
|
||||||
|
*/
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const key = currentField.value?.key
|
||||||
|
|
||||||
|
try {
|
||||||
|
uni.showLoading()
|
||||||
|
|
||||||
|
// 构建更新数据对象
|
||||||
|
let updateData: any = Object.assign({}, userInfo.value)
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'email':
|
||||||
|
// 更新邮箱
|
||||||
|
if (!editForm.value.email || !editForm.value.code) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: t('user.pleaseInputCode'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await updateEmail(userInfo.value.id, editForm.value.email, editForm.value.code)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'password':
|
||||||
|
// 更新密码
|
||||||
|
if (!passwordOk.value) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: passwordNote.value,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (editForm.value.password !== editForm.value.confirmPassword) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: t('user.passwordNotMatch'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await updatePassword(userInfo.value.id, editForm.value.password)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'avatar':
|
||||||
|
// 更新头像
|
||||||
|
console.log('avatarUrl.value:', avatarUrl.value)
|
||||||
|
if (!avatarUrl.value) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: t('common.pleaseSelect') + t('user.avatar'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是新上传的图片,需要先上传
|
||||||
|
updateData.avatar = avatarUrl.value
|
||||||
|
await updateUserInfo(updateData)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'sex':
|
||||||
|
// 更新性别
|
||||||
|
const sexValue = editValue.value === 2 ? 0 : editValue.value
|
||||||
|
updateData.sex = sexValue
|
||||||
|
await updateUserInfo(updateData)
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
// 更新其他字段
|
||||||
|
if (!editValue.value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: getPlaceholder(key),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateData[key] = editValue.value
|
||||||
|
await updateUserInfo(updateData)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: t('user.updateSuccess'),
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
closeModal()
|
||||||
|
|
||||||
|
// 刷新数据
|
||||||
|
setTimeout(() => {
|
||||||
|
getData()
|
||||||
|
}, 500)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新失败:', error)
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭弹窗
|
||||||
|
*/
|
||||||
|
const closeModal = () => {
|
||||||
|
showEditModal.value = false
|
||||||
|
currentField.value = null
|
||||||
|
if (codeTimer) {
|
||||||
|
clearInterval(codeTimer)
|
||||||
|
codeDisabled.value = false
|
||||||
|
codeText.value = t('login.getCode')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$theme-color: #54a966;
|
||||||
|
|
||||||
|
.profile-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f7faf9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-navbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1rpx solid #e5e5e5;
|
||||||
|
|
||||||
|
.navbar-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 44px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.navbar-left {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-content {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
padding: 40rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 180rpx;
|
||||||
|
height: 180rpx;
|
||||||
|
border-radius: 180rpx;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: $theme-color;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-list {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: #666;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
color: #999;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-modal {
|
||||||
|
padding: 60rpx 50rpx 80rpx;
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #555;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 50rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-note {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
line-height: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
border-bottom: 1rpx solid #ededed;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #888;
|
||||||
|
margin-top: 25rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
340
pages/user/settings/index.vue
Normal file
340
pages/user/settings/index.vue
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
<template>
|
||||||
|
<view class="settings-page">
|
||||||
|
<!-- 自定义导航栏 -->
|
||||||
|
<nav-bar :title="$t('user.settings')"></nav-bar>
|
||||||
|
|
||||||
|
<view class="settings-content">
|
||||||
|
<!-- 设置列表 -->
|
||||||
|
<view class="settings-list">
|
||||||
|
<view
|
||||||
|
v-for="item in settingItems"
|
||||||
|
:key="item.id"
|
||||||
|
class="setting-item"
|
||||||
|
@click="handleSettingClick(item)"
|
||||||
|
>
|
||||||
|
<text class="label">{{ item.label }}</text>
|
||||||
|
<view class="value-wrapper">
|
||||||
|
<text class="value">{{ item.value }}</text>
|
||||||
|
<wd-icon name="arrow-right" size="16px" color="#aaa" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<view class="action-buttons">
|
||||||
|
<wd-button type="info" block :round="false" custom-class="logout-btn" @click="handleLogoff">{{ $t('user.logoff') }}</wd-button>
|
||||||
|
<wd-button type="error" block :round="false" @click="handleLogout">
|
||||||
|
{{ $t('user.logout') }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 微信二维码弹窗 -->
|
||||||
|
<wd-popup v-model="showQrCode" position="bottom" :closeable="true">
|
||||||
|
<view class="qrcode-modal">
|
||||||
|
<text class="modal-title">{{ $t('user.wechatTip') }}</text>
|
||||||
|
<image
|
||||||
|
src="/static/qiyeWx.jpg"
|
||||||
|
mode="aspectFit"
|
||||||
|
class="qrcode-image"
|
||||||
|
@click="previewQrCode"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
|
||||||
|
<wd-message-box />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useMessage } from '@/uni_modules/wot-design-uni'
|
||||||
|
import { makePhoneCall, copyToClipboard } from '@/utils/index'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
// 导航栏高度
|
||||||
|
const statusBarHeight = ref(0)
|
||||||
|
const navbarHeight = ref('44px')
|
||||||
|
|
||||||
|
// 设置项列表
|
||||||
|
const settingItems = computed(() => [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
label: t('user.hotline'),
|
||||||
|
value: '022-24142321',
|
||||||
|
type: 'tel'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
label: t('user.customerEmail'),
|
||||||
|
value: 'appyilujiankang@sina.com',
|
||||||
|
type: 'email'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
label: t('user.wechat'),
|
||||||
|
value: '',
|
||||||
|
type: 'wechat'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
label: t('user.checkVersion'),
|
||||||
|
value: '',
|
||||||
|
type: 'version'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 弹窗状态
|
||||||
|
const showQrCode = ref(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导航栏高度
|
||||||
|
*/
|
||||||
|
const getNavbarHeight = () => {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
statusBarHeight.value = systemInfo.statusBarHeight || 0
|
||||||
|
|
||||||
|
let navBarHeight = 44
|
||||||
|
if (systemInfo.model.indexOf('iPhone') !== -1 && parseInt(systemInfo.model.slice(-2)) >= 11) {
|
||||||
|
navBarHeight = 48
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalHeight = statusBarHeight.value + navBarHeight
|
||||||
|
navbarHeight.value = totalHeight + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理设置项点击
|
||||||
|
*/
|
||||||
|
const handleSettingClick = (item: any) => {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'tel':
|
||||||
|
handlePhoneCall(item.value, item.label)
|
||||||
|
break
|
||||||
|
case 'email':
|
||||||
|
handleCopyEmail(item.value, item.label)
|
||||||
|
break
|
||||||
|
case 'wechat':
|
||||||
|
showQrCode.value = true
|
||||||
|
break
|
||||||
|
case 'version':
|
||||||
|
checkVersion()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拨打电话
|
||||||
|
*/
|
||||||
|
const handlePhoneCall = (phoneNumber: string, title: string) => {
|
||||||
|
makePhoneCall(phoneNumber, title, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制到剪贴板
|
||||||
|
*/
|
||||||
|
const handleCopyEmail = (content: string, title: string) => {
|
||||||
|
copyToClipboard(content, title, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览二维码
|
||||||
|
*/
|
||||||
|
const previewQrCode = () => {
|
||||||
|
showQrCode.value = false
|
||||||
|
uni.previewImage({
|
||||||
|
urls: ['/static/qiyeWx.jpg']
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查版本更新
|
||||||
|
*/
|
||||||
|
const checkVersion = () => {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
// TODO: 集成 uni-upgrade-center-app 插件
|
||||||
|
uni.showToast({
|
||||||
|
title: '当前已是最新版本',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef APP-PLUS
|
||||||
|
uni.showToast({
|
||||||
|
title: '仅支持APP端检查更新',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注销账户
|
||||||
|
*/
|
||||||
|
const handleLogoff = () => {
|
||||||
|
message.confirm({
|
||||||
|
title: t('global.tips'),
|
||||||
|
msg: t('user.logoffConfirm'),
|
||||||
|
}).then(() => {
|
||||||
|
message.confirm({
|
||||||
|
title: t('global.tips'),
|
||||||
|
msg: t('user.logoffConfirmAgain'),
|
||||||
|
}).then(() => {
|
||||||
|
performLogout()
|
||||||
|
}).catch(() => {
|
||||||
|
// 取消注销账户
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
// 取消注销账户
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
*/
|
||||||
|
const handleLogout = () => {
|
||||||
|
message.confirm({
|
||||||
|
title: t('global.tips'),
|
||||||
|
msg: t('user.logoutConfirm'),
|
||||||
|
}).then(() => {
|
||||||
|
performLogout()
|
||||||
|
}).catch(() => {
|
||||||
|
// 取消退出登录
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行退出登录
|
||||||
|
*/
|
||||||
|
const performLogout = () => {
|
||||||
|
// 清除用户信息
|
||||||
|
userStore.logout()
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/login/login'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getNavbarHeight()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$theme-color: #54a966;
|
||||||
|
|
||||||
|
.settings-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f7faf9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-navbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1rpx solid #e5e5e5;
|
||||||
|
|
||||||
|
.navbar-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 44px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.navbar-left {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-content {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-list {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 30rpx;
|
||||||
|
border-bottom: 1rpx solid #e0e0e0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #b0b0b0;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
padding: 40rpx 30rpx 60rpx;
|
||||||
|
|
||||||
|
:deep(.logout-btn) {
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
color: #8a1a1a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode-modal {
|
||||||
|
padding: 40rpx;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode-image {
|
||||||
|
width: 400rpx;
|
||||||
|
height: 400rpx;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
523
pages/user/wallet/index.vue
Normal file
523
pages/user/wallet/index.vue
Normal file
@@ -0,0 +1,523 @@
|
|||||||
|
<template>
|
||||||
|
<view class="wallet-page">
|
||||||
|
<!-- 自定义导航栏 -->
|
||||||
|
<nav-bar :title="$t('user.subscribe')"></nav-bar>
|
||||||
|
|
||||||
|
<view class="wallet-content">
|
||||||
|
<!-- VIP套餐列表 -->
|
||||||
|
<view class="package-section">
|
||||||
|
<text class="section-title">{{ $t('user.subscribe') }}</text>
|
||||||
|
<view class="package-list">
|
||||||
|
<view
|
||||||
|
v-for="(pkg, index) in packages"
|
||||||
|
:key="pkg.id"
|
||||||
|
:class="['package-item', { active: selectedIndex === index }]"
|
||||||
|
@click="selectPackage(index)"
|
||||||
|
>
|
||||||
|
<view class="package-price">
|
||||||
|
<image src="/static/icon/currency.png" class="currency-icon" />
|
||||||
|
<text class="price">{{ pkg.dictType }}</text>
|
||||||
|
<text class="unit">NZD</text>
|
||||||
|
</view>
|
||||||
|
<text class="package-duration">{{ getDuration(pkg.remark) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 支付方式 -->
|
||||||
|
<view class="payment-section">
|
||||||
|
<text class="section-title">{{ $t('user.paymentMethod') }}</text>
|
||||||
|
<radio-group class="payment-group">
|
||||||
|
<view class="payment-item">
|
||||||
|
<radio :checked="true" color="#54a966" />
|
||||||
|
<text>{{ paymentName }}</text>
|
||||||
|
</view>
|
||||||
|
</radio-group>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 协议勾选 -->
|
||||||
|
<view class="agreement-section">
|
||||||
|
<view class="agreement-checkbox" @click="toggleAgreement">
|
||||||
|
<view :class="['checkbox', { checked: agreed }]">
|
||||||
|
<wd-icon v-if="agreed" name="check" size="16px" color="#fff" />
|
||||||
|
</view>
|
||||||
|
<text class="agreement-text">
|
||||||
|
{{ $t('user.agreeText') }}
|
||||||
|
<text class="link" @click.stop="showAgreementModal">{{ $t('user.agreement') }}</text>
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 订阅按钮 -->
|
||||||
|
<view class="subscribe-button">
|
||||||
|
<wd-button
|
||||||
|
type="success"
|
||||||
|
block
|
||||||
|
@click="handleSubscribe"
|
||||||
|
>
|
||||||
|
{{ $t('user.subscribe') }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 协议弹窗 -->
|
||||||
|
<wd-popup v-model="agreementShow" position="bottom" :closeable="true">
|
||||||
|
<view class="agreement-modal">
|
||||||
|
<view class="modal-title">{{ agreementData.title }}</view>
|
||||||
|
<scroll-view scroll-y class="modal-content">
|
||||||
|
<view v-html="agreementData.content"></view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { getVipPackages, createOrder, verifyGooglePay, verifyIAP } from '@/api/modules/user'
|
||||||
|
import { commonApi } from '@/api/modules/common'
|
||||||
|
import type { IVipPackage } from '@/types/user'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 导航栏高度
|
||||||
|
const statusBarHeight = ref(0)
|
||||||
|
const navbarHeight = ref('44px')
|
||||||
|
|
||||||
|
// VIP套餐列表
|
||||||
|
const packages = ref<IVipPackage[]>([])
|
||||||
|
const selectedIndex = ref<number | null>(null)
|
||||||
|
const selectedPackage = computed(() =>
|
||||||
|
selectedIndex.value !== null ? packages.value[selectedIndex.value] : null
|
||||||
|
)
|
||||||
|
|
||||||
|
// 平台和支付方式
|
||||||
|
const platform = ref('')
|
||||||
|
const paymentMethod = ref(5) // 3-iOS IAP, 5-Google Pay
|
||||||
|
const paymentName = computed(() =>
|
||||||
|
platform.value === 'android' ? 'Google Pay' : 'Apple Pay'
|
||||||
|
)
|
||||||
|
|
||||||
|
// 协议相关
|
||||||
|
const agreed = ref(false)
|
||||||
|
const agreementShow = ref(false)
|
||||||
|
const agreementData = ref({
|
||||||
|
title: '',
|
||||||
|
content: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 订单号
|
||||||
|
const orderSn = ref('')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导航栏高度
|
||||||
|
*/
|
||||||
|
const getNavbarHeight = () => {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
statusBarHeight.value = systemInfo.statusBarHeight || 0
|
||||||
|
|
||||||
|
let navBarHeight = 44
|
||||||
|
if (systemInfo.model.indexOf('iPhone') !== -1 && parseInt(systemInfo.model.slice(-2)) >= 11) {
|
||||||
|
navBarHeight = 48
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalHeight = statusBarHeight.value + navBarHeight
|
||||||
|
navbarHeight.value = totalHeight + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取平台信息
|
||||||
|
*/
|
||||||
|
const getPlatform = () => {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
platform.value = systemInfo.platform === 'android' ? 'android' : 'ios'
|
||||||
|
paymentMethod.value = platform.value === 'android' ? 5 : 3
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取VIP套餐列表
|
||||||
|
*/
|
||||||
|
const getPackages = async () => {
|
||||||
|
try {
|
||||||
|
uni.showLoading({
|
||||||
|
title: t('common.loading')
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await getVipPackages()
|
||||||
|
|
||||||
|
uni.hideLoading()
|
||||||
|
|
||||||
|
if (res.sysDictDatas && res.sysDictDatas.length > 0) {
|
||||||
|
packages.value = res.sysDictDatas
|
||||||
|
|
||||||
|
// 检查是否有缓存的选择
|
||||||
|
const cachedIndex = uni.getStorageSync('selectVipCard')
|
||||||
|
if (cachedIndex !== null && cachedIndex < packages.value.length) {
|
||||||
|
selectedIndex.value = cachedIndex
|
||||||
|
} else {
|
||||||
|
selectedIndex.value = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取套餐列表失败:', error)
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择套餐
|
||||||
|
*/
|
||||||
|
const selectPackage = (index: number) => {
|
||||||
|
selectedIndex.value = index
|
||||||
|
uni.setStorageSync('selectVipCard', index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取时长文本
|
||||||
|
*/
|
||||||
|
const getDuration = (remark: string) => {
|
||||||
|
const days = parseInt(remark)
|
||||||
|
if (days === 30) {
|
||||||
|
return t('user.monthCard')
|
||||||
|
} else if (days === 90) {
|
||||||
|
return t('user.seasonCard')
|
||||||
|
} else if (days === 365) {
|
||||||
|
return t('user.yearCard')
|
||||||
|
}
|
||||||
|
return `${days} ${t('user.days')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换协议勾选
|
||||||
|
*/
|
||||||
|
const toggleAgreement = () => {
|
||||||
|
agreed.value = !agreed.value
|
||||||
|
uni.setStorageSync('Agreements_agreed', agreed.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示协议弹窗
|
||||||
|
*/
|
||||||
|
const showAgreementModal = async () => {
|
||||||
|
try {
|
||||||
|
const res = await commonApi.getAgreement(113)
|
||||||
|
if (res) {
|
||||||
|
let content = res.content || ''
|
||||||
|
content = content.replace(
|
||||||
|
/<h5>/g,
|
||||||
|
'<view style="font-weight: bold;font-size: 32rpx;margin-top: 20rpx;margin-bottom: 20rpx;">'
|
||||||
|
)
|
||||||
|
content = content.replace(/<\/h5>/g, '</view>')
|
||||||
|
|
||||||
|
agreementData.value = {
|
||||||
|
title: res.title,
|
||||||
|
content: content
|
||||||
|
}
|
||||||
|
agreementShow.value = true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取协议失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅处理
|
||||||
|
*/
|
||||||
|
const handleSubscribe = async () => {
|
||||||
|
// 验证是否勾选协议
|
||||||
|
if (!agreed.value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('user.agreeFirst'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证是否选择套餐
|
||||||
|
if (selectedIndex.value === null || !selectedPackage.value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('user.selectPackage'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
uni.showLoading({
|
||||||
|
title: t('common.loading')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建订单
|
||||||
|
const orderData = {
|
||||||
|
paymentMethod: paymentMethod.value,
|
||||||
|
orderMoney: selectedPackage.value.money,
|
||||||
|
vipBuyConfigId: selectedPackage.value.priceTypeId,
|
||||||
|
orderType: 'abroadVip',
|
||||||
|
abroadVipId: selectedPackage.value.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderRes = await createOrder(orderData)
|
||||||
|
|
||||||
|
if (orderRes.orderSn) {
|
||||||
|
orderSn.value = orderRes.orderSn
|
||||||
|
|
||||||
|
// 根据平台调起支付
|
||||||
|
if (platform.value === 'android') {
|
||||||
|
await initiateGooglePay()
|
||||||
|
} else {
|
||||||
|
await initiateIAP()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建订单失败:', error)
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Google Pay 支付(Android)
|
||||||
|
*/
|
||||||
|
const initiateGooglePay = async () => {
|
||||||
|
// TODO: 集成 Google Pay SDK
|
||||||
|
// 这里需要使用 sn-googlepay5 插件
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: 'Google Pay 功能开发中',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IAP 支付(iOS)
|
||||||
|
*/
|
||||||
|
const initiateIAP = async () => {
|
||||||
|
// TODO: 集成 IAP SDK
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: 'IAP 功能开发中',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回上一页
|
||||||
|
*/
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getNavbarHeight()
|
||||||
|
getPlatform()
|
||||||
|
getPackages()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 清除缓存
|
||||||
|
uni.removeStorageSync('selectVipCard')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$theme-color: #54a966;
|
||||||
|
|
||||||
|
.wallet-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f7faf9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-navbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1rpx solid #e5e5e5;
|
||||||
|
|
||||||
|
.navbar-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 44px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.navbar-left {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-content {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
display: block;
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: -10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-item {
|
||||||
|
width: calc(33.333% - 20rpx);
|
||||||
|
margin: 10rpx;
|
||||||
|
padding: 30rpx 0;
|
||||||
|
border: 2rpx solid #ebebeb;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: $theme-color;
|
||||||
|
background: rgba(84, 169, 102, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-price {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
.currency-icon {
|
||||||
|
width: 25rpx;
|
||||||
|
height: 25rpx;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #333;
|
||||||
|
margin-left: 5rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-duration {
|
||||||
|
display: block;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-group {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10rpx 0;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-section {
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border: 2rpx solid #ddd;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 15rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 2rpx;
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
background-color: $theme-color;
|
||||||
|
border-color: $theme-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
line-height: 40rpx;
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: $theme-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscribe-button {
|
||||||
|
padding: 40rpx 30rpx 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-modal {
|
||||||
|
padding: 40rpx 30rpx;
|
||||||
|
max-height: 70vh;
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
max-height: 60vh;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #555;
|
||||||
|
line-height: 45rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
242
pages/user/wallet/list.vue
Normal file
242
pages/user/wallet/list.vue
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
<template>
|
||||||
|
<view class="transaction-page">
|
||||||
|
<!-- 自定义导航栏 -->
|
||||||
|
<view class="custom-navbar" :style="{ height: navbarHeight }">
|
||||||
|
<view class="navbar-content" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
|
<view class="navbar-left" @click="goBack">
|
||||||
|
<wd-icon name="arrow-left" size="18px" color="#333" />
|
||||||
|
</view>
|
||||||
|
<text class="navbar-title">{{ $t('user.myAccount') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 交易记录列表 -->
|
||||||
|
<view
|
||||||
|
v-if="transactionList.length > 0"
|
||||||
|
class="transaction-list"
|
||||||
|
:style="{ paddingTop: navbarHeight }"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="item in transactionList"
|
||||||
|
:key="item.id"
|
||||||
|
class="transaction-item"
|
||||||
|
>
|
||||||
|
<view class="item-header">
|
||||||
|
<text class="transaction-type">{{ item.orderType }}</text>
|
||||||
|
<text
|
||||||
|
:class="['amount', { positive: item.orderType === '充值' }]"
|
||||||
|
>
|
||||||
|
{{ item.orderType === '充值' ? '+' : '' }}{{ item.changeAmount }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<text class="remark">{{ formatRemark(item.remark) }}</text>
|
||||||
|
<text class="create-time">{{ item.createTime }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<view
|
||||||
|
v-else-if="!loading"
|
||||||
|
class="empty-state"
|
||||||
|
:style="{ paddingTop: navbarHeight }"
|
||||||
|
>
|
||||||
|
<text class="empty-text">{{ $t('common.noData') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { getTransactionList } from '@/api/modules/user'
|
||||||
|
import type { ITransaction } from '@/types/user'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 导航栏高度
|
||||||
|
const statusBarHeight = ref(0)
|
||||||
|
const navbarHeight = ref('44px')
|
||||||
|
|
||||||
|
// 交易记录列表
|
||||||
|
const transactionList = ref<ITransaction[]>([])
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导航栏高度
|
||||||
|
*/
|
||||||
|
const getNavbarHeight = () => {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
statusBarHeight.value = systemInfo.statusBarHeight || 0
|
||||||
|
|
||||||
|
let navBarHeight = 44
|
||||||
|
if (systemInfo.model.indexOf('iPhone') !== -1 && parseInt(systemInfo.model.slice(-2)) >= 11) {
|
||||||
|
navBarHeight = 48
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalHeight = statusBarHeight.value + navBarHeight
|
||||||
|
navbarHeight.value = totalHeight + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取交易记录
|
||||||
|
*/
|
||||||
|
const getData = async () => {
|
||||||
|
if (!userStore.userInfo.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
uni.showLoading({
|
||||||
|
title: t('common.loading')
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await getTransactionList(userStore.userInfo.id)
|
||||||
|
|
||||||
|
uni.hideLoading()
|
||||||
|
|
||||||
|
if (res.transactionDetailsList && res.transactionDetailsList.length > 0) {
|
||||||
|
transactionList.value = res.transactionDetailsList
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取交易记录失败:', error)
|
||||||
|
uni.hideLoading()
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化备注信息(换行显示订单编号)
|
||||||
|
*/
|
||||||
|
const formatRemark = (remark: string) => {
|
||||||
|
if (remark.includes('Stripe充值:')) {
|
||||||
|
return remark.replace('Stripe充值:', 'Stripe充值:\n')
|
||||||
|
} else if (remark.includes('订单编号为 - ')) {
|
||||||
|
return remark.replace('订单编号为 - ', '订单编号为 - \n')
|
||||||
|
}
|
||||||
|
return remark
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回上一页
|
||||||
|
*/
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getNavbarHeight()
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$theme-color: #54a966;
|
||||||
|
|
||||||
|
.transaction-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f7faf9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-navbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1rpx solid #e5e5e5;
|
||||||
|
|
||||||
|
.navbar-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 44px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.navbar-left {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-list {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-item {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.item-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
.transaction-type {
|
||||||
|
font-size: 34rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
font-size: 34rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&.positive {
|
||||||
|
color: $theme-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark {
|
||||||
|
display: block;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 38rpx;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-time {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 60vh;
|
||||||
|
padding-top: 100rpx;
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||||
"Courier New", monospace;
|
"Courier New", monospace;
|
||||||
--color-red-500: oklch(63.7% 0.237 25.331);
|
--color-red-500: oklch(63.7% 0.237 25.331);
|
||||||
--color-emerald-600: oklch(59.6% 0.145 163.225);
|
|
||||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
--default-transition-duration: 150ms;
|
--default-transition-duration: 150ms;
|
||||||
@@ -187,9 +186,6 @@
|
|||||||
.sticky {
|
.sticky {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
}
|
}
|
||||||
.isolate {
|
|
||||||
isolation: isolate;
|
|
||||||
}
|
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@media (width >= 40rem) {
|
@media (width >= 40rem) {
|
||||||
@@ -211,9 +207,6 @@
|
|||||||
.block {
|
.block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.contents {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
@@ -229,12 +222,15 @@
|
|||||||
.inline-block {
|
.inline-block {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.list-item {
|
|
||||||
display: list-item;
|
|
||||||
}
|
|
||||||
.table {
|
.table {
|
||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
|
.w-\[100px\] {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
.flex-1 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
.flex-shrink {
|
.flex-shrink {
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
}
|
}
|
||||||
@@ -244,6 +240,9 @@
|
|||||||
.resize {
|
.resize {
|
||||||
resize: both;
|
resize: both;
|
||||||
}
|
}
|
||||||
|
.flex-wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
.border {
|
.border {
|
||||||
border-style: var(--tw-border-style);
|
border-style: var(--tw-border-style);
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
@@ -251,35 +250,25 @@
|
|||||||
.bg-\[red\] {
|
.bg-\[red\] {
|
||||||
background-color: red;
|
background-color: red;
|
||||||
}
|
}
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.text-left {
|
.text-left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
.text-\[\#fff\] {
|
.text-\[\#fff\] {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.capitalize {
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
.lowercase {
|
.lowercase {
|
||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
}
|
}
|
||||||
.uppercase {
|
.uppercase {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
.italic {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
.ordinal {
|
.ordinal {
|
||||||
--tw-ordinal: ordinal;
|
--tw-ordinal: ordinal;
|
||||||
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
|
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
|
||||||
}
|
}
|
||||||
.underline {
|
|
||||||
text-decoration-line: underline;
|
|
||||||
}
|
|
||||||
.shadow {
|
|
||||||
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
|
||||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
||||||
}
|
|
||||||
.ring {
|
.ring {
|
||||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
@@ -304,10 +293,6 @@
|
|||||||
--tw-ease: var(--ease-in-out);
|
--tw-ease: var(--ease-in-out);
|
||||||
transition-timing-function: var(--ease-in-out);
|
transition-timing-function: var(--ease-in-out);
|
||||||
}
|
}
|
||||||
.ease-out {
|
|
||||||
--tw-ease: var(--ease-out);
|
|
||||||
transition-timing-function: var(--ease-out);
|
|
||||||
}
|
|
||||||
.hover\:bg-red-500 {
|
.hover\:bg-red-500 {
|
||||||
&:hover {
|
&:hover {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
|
|||||||
@@ -4,20 +4,22 @@ import { setAuthToken, clearAuthToken } from '@/utils/auth'
|
|||||||
import type { IUserInfo } from '@/types/user'
|
import type { IUserInfo } from '@/types/user'
|
||||||
|
|
||||||
export const useUserStore = defineStore('user', {
|
export const useUserStore = defineStore('user', {
|
||||||
state: (): IUserInfo => ({
|
state: (): IUserInfo => (uni.getStorageSync('userInfo') || {
|
||||||
id: 0,
|
id: 0,
|
||||||
name: '',
|
name: '',
|
||||||
|
nickname: '',
|
||||||
avatar: '',
|
avatar: '',
|
||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
token: '',
|
||||||
token: uni.getStorageSync('token') || '',
|
age: '',
|
||||||
|
tel: '',
|
||||||
|
sex: '',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
isLoggedIn: (state: any) => Boolean(state.token),
|
isLoggedIn: (state: any) => Boolean(state.token),
|
||||||
userInfo: (state: any) => {
|
userInfo: (state: any) => {
|
||||||
const { id, name, avatar, email, phone } = state
|
return state
|
||||||
return { id, name, avatar, email, phone }
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -54,7 +56,6 @@ export const useUserStore = defineStore('user', {
|
|||||||
|
|
||||||
clearAuthToken()
|
clearAuthToken()
|
||||||
uni.removeStorageSync('userInfo')
|
uni.removeStorageSync('userInfo')
|
||||||
uni.reLaunch({ url: '/pages/user/login' })
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 从本地存储恢复用户信息 */
|
/** 从本地存储恢复用户信息 */
|
||||||
|
|||||||
536
style/tailwind.css
Normal file
536
style/tailwind.css
Normal file
@@ -0,0 +1,536 @@
|
|||||||
|
/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */
|
||||||
|
@layer properties;
|
||||||
|
@layer theme, base, components, utilities;
|
||||||
|
@layer theme {
|
||||||
|
:root, :host {
|
||||||
|
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
|
||||||
|
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||||
|
"Courier New", monospace;
|
||||||
|
--color-red-500: oklch(63.7% 0.237 25.331);
|
||||||
|
--color-emerald-600: oklch(59.6% 0.145 163.225);
|
||||||
|
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
--default-transition-duration: 150ms;
|
||||||
|
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
--default-font-family: var(--font-sans);
|
||||||
|
--default-mono-font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@layer base {
|
||||||
|
*, ::after, ::before, ::backdrop, ::file-selector-button {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0 solid;
|
||||||
|
}
|
||||||
|
html, :host {
|
||||||
|
line-height: 1.5;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
tab-size: 4;
|
||||||
|
font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
|
||||||
|
font-feature-settings: var(--default-font-feature-settings, normal);
|
||||||
|
font-variation-settings: var(--default-font-variation-settings, normal);
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
height: 0;
|
||||||
|
color: inherit;
|
||||||
|
border-top-width: 1px;
|
||||||
|
}
|
||||||
|
abbr:where([title]) {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
-webkit-text-decoration: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
b, strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
code, kbd, samp, pre {
|
||||||
|
font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
|
||||||
|
font-feature-settings: var(--default-mono-font-feature-settings, normal);
|
||||||
|
font-variation-settings: var(--default-mono-font-variation-settings, normal);
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
sub, sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
text-indent: 0;
|
||||||
|
border-color: inherit;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
:-moz-focusring {
|
||||||
|
outline: auto;
|
||||||
|
}
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
ol, ul, menu {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
img, svg, video, canvas, audio, iframe, embed, object {
|
||||||
|
display: block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
img, video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
button, input, select, optgroup, textarea, ::file-selector-button {
|
||||||
|
font: inherit;
|
||||||
|
font-feature-settings: inherit;
|
||||||
|
font-variation-settings: inherit;
|
||||||
|
letter-spacing: inherit;
|
||||||
|
color: inherit;
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
:where(select:is([multiple], [size])) optgroup {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
:where(select:is([multiple], [size])) optgroup option {
|
||||||
|
padding-inline-start: 20px;
|
||||||
|
}
|
||||||
|
::file-selector-button {
|
||||||
|
margin-inline-end: 4px;
|
||||||
|
}
|
||||||
|
::placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
|
||||||
|
::placeholder {
|
||||||
|
color: currentcolor;
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
color: color-mix(in oklab, currentcolor 50%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
::-webkit-date-and-time-value {
|
||||||
|
min-height: 1lh;
|
||||||
|
text-align: inherit;
|
||||||
|
}
|
||||||
|
::-webkit-datetime-edit {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
::-webkit-datetime-edit-fields-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
|
||||||
|
padding-block: 0;
|
||||||
|
}
|
||||||
|
::-webkit-calendar-picker-indicator {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
:-moz-ui-invalid {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {
|
||||||
|
appearance: button;
|
||||||
|
}
|
||||||
|
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
[hidden]:where(:not([hidden="until-found"])) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@layer utilities {
|
||||||
|
.collapse {
|
||||||
|
visibility: collapse;
|
||||||
|
}
|
||||||
|
.visible {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
.absolute {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.fixed {
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
.relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.static {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
.sticky {
|
||||||
|
position: sticky;
|
||||||
|
}
|
||||||
|
.isolate {
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
@media (width >= 40rem) {
|
||||||
|
max-width: 40rem;
|
||||||
|
}
|
||||||
|
@media (width >= 48rem) {
|
||||||
|
max-width: 48rem;
|
||||||
|
}
|
||||||
|
@media (width >= 64rem) {
|
||||||
|
max-width: 64rem;
|
||||||
|
}
|
||||||
|
@media (width >= 80rem) {
|
||||||
|
max-width: 80rem;
|
||||||
|
}
|
||||||
|
@media (width >= 96rem) {
|
||||||
|
max-width: 96rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.contents {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.inline {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.inline-block {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.list-item {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
.table {
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
.flex-shrink {
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
.transform {
|
||||||
|
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
||||||
|
}
|
||||||
|
.resize {
|
||||||
|
resize: both;
|
||||||
|
}
|
||||||
|
.border {
|
||||||
|
border-style: var(--tw-border-style);
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
.bg-\[red\] {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
.text-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.text-\[\#fff\] {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.capitalize {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.lowercase {
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
|
.uppercase {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.ordinal {
|
||||||
|
--tw-ordinal: ordinal;
|
||||||
|
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
|
||||||
|
}
|
||||||
|
.underline {
|
||||||
|
text-decoration-line: underline;
|
||||||
|
}
|
||||||
|
.shadow {
|
||||||
|
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||||
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
|
}
|
||||||
|
.ring {
|
||||||
|
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||||
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
|
}
|
||||||
|
.outline {
|
||||||
|
outline-style: var(--tw-outline-style);
|
||||||
|
outline-width: 1px;
|
||||||
|
}
|
||||||
|
.blur {
|
||||||
|
--tw-blur: blur(8px);
|
||||||
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||||
|
}
|
||||||
|
.filter {
|
||||||
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||||
|
}
|
||||||
|
.transition {
|
||||||
|
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
|
||||||
|
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||||
|
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||||
|
}
|
||||||
|
.ease-in-out {
|
||||||
|
--tw-ease: var(--ease-in-out);
|
||||||
|
transition-timing-function: var(--ease-in-out);
|
||||||
|
}
|
||||||
|
.ease-out {
|
||||||
|
--tw-ease: var(--ease-out);
|
||||||
|
transition-timing-function: var(--ease-out);
|
||||||
|
}
|
||||||
|
.hover\:bg-red-500 {
|
||||||
|
&:hover {
|
||||||
|
@media (hover: hover) {
|
||||||
|
background-color: var(--color-red-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@property --tw-rotate-x {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-rotate-y {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-rotate-z {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-skew-x {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-skew-y {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-border-style {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: solid;
|
||||||
|
}
|
||||||
|
@property --tw-ordinal {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-slashed-zero {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-numeric-figure {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-numeric-spacing {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-numeric-fraction {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-shadow {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0 0 #0000;
|
||||||
|
}
|
||||||
|
@property --tw-shadow-color {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-shadow-alpha {
|
||||||
|
syntax: "<percentage>";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 100%;
|
||||||
|
}
|
||||||
|
@property --tw-inset-shadow {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0 0 #0000;
|
||||||
|
}
|
||||||
|
@property --tw-inset-shadow-color {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-inset-shadow-alpha {
|
||||||
|
syntax: "<percentage>";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 100%;
|
||||||
|
}
|
||||||
|
@property --tw-ring-color {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-ring-shadow {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0 0 #0000;
|
||||||
|
}
|
||||||
|
@property --tw-inset-ring-color {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-inset-ring-shadow {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0 0 #0000;
|
||||||
|
}
|
||||||
|
@property --tw-ring-inset {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-ring-offset-width {
|
||||||
|
syntax: "<length>";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0px;
|
||||||
|
}
|
||||||
|
@property --tw-ring-offset-color {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: #fff;
|
||||||
|
}
|
||||||
|
@property --tw-ring-offset-shadow {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0 0 #0000;
|
||||||
|
}
|
||||||
|
@property --tw-outline-style {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: solid;
|
||||||
|
}
|
||||||
|
@property --tw-blur {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-brightness {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-contrast {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-grayscale {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-hue-rotate {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-invert {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-opacity {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-saturate {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-sepia {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-drop-shadow {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-drop-shadow-color {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-drop-shadow-alpha {
|
||||||
|
syntax: "<percentage>";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 100%;
|
||||||
|
}
|
||||||
|
@property --tw-drop-shadow-size {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-ease {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@layer properties {
|
||||||
|
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
||||||
|
*, ::before, ::after, ::backdrop {
|
||||||
|
--tw-rotate-x: initial;
|
||||||
|
--tw-rotate-y: initial;
|
||||||
|
--tw-rotate-z: initial;
|
||||||
|
--tw-skew-x: initial;
|
||||||
|
--tw-skew-y: initial;
|
||||||
|
--tw-border-style: solid;
|
||||||
|
--tw-ordinal: initial;
|
||||||
|
--tw-slashed-zero: initial;
|
||||||
|
--tw-numeric-figure: initial;
|
||||||
|
--tw-numeric-spacing: initial;
|
||||||
|
--tw-numeric-fraction: initial;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-color: initial;
|
||||||
|
--tw-shadow-alpha: 100%;
|
||||||
|
--tw-inset-shadow: 0 0 #0000;
|
||||||
|
--tw-inset-shadow-color: initial;
|
||||||
|
--tw-inset-shadow-alpha: 100%;
|
||||||
|
--tw-ring-color: initial;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-inset-ring-color: initial;
|
||||||
|
--tw-inset-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-inset: initial;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-outline-style: solid;
|
||||||
|
--tw-blur: initial;
|
||||||
|
--tw-brightness: initial;
|
||||||
|
--tw-contrast: initial;
|
||||||
|
--tw-grayscale: initial;
|
||||||
|
--tw-hue-rotate: initial;
|
||||||
|
--tw-invert: initial;
|
||||||
|
--tw-opacity: initial;
|
||||||
|
--tw-saturate: initial;
|
||||||
|
--tw-sepia: initial;
|
||||||
|
--tw-drop-shadow: initial;
|
||||||
|
--tw-drop-shadow-color: initial;
|
||||||
|
--tw-drop-shadow-alpha: 100%;
|
||||||
|
--tw-drop-shadow-size: initial;
|
||||||
|
--tw-ease: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
137
style/ui.scss
Normal file
137
style/ui.scss
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
:root,
|
||||||
|
page {
|
||||||
|
/* 文字字号 */
|
||||||
|
--wot-fs-title: 18px; // 标题字号/重要正文字号
|
||||||
|
--wot-fs-content: 16px; // 普通正文
|
||||||
|
--wot-fs-secondary: 14px; // 次要信息,注释/补充/正文
|
||||||
|
|
||||||
|
// 导航栏
|
||||||
|
// --wot-navbar-background: #5355C8;
|
||||||
|
// --wot-navbar-color: #fff;
|
||||||
|
// --wot-navbar-hover-color: #4D4DB9;
|
||||||
|
|
||||||
|
// 底部tabbar
|
||||||
|
// --wot-tabbar-height: 60px;
|
||||||
|
// --wot-tabbar-item-title-font-size: 16px;
|
||||||
|
|
||||||
|
// cell
|
||||||
|
--wot-cell-title-fs: 16px;
|
||||||
|
|
||||||
|
// dropdown
|
||||||
|
// --wot-drop-menu-selected-color: #5355C8;
|
||||||
|
|
||||||
|
// collapse
|
||||||
|
--wot-collapse-body-padding: 0px 20px 15px;
|
||||||
|
}
|
||||||
|
uni-textarea {
|
||||||
|
height: 100px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单
|
||||||
|
.wd-input__body, .wd-picker__body {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.wd-input__error-message,
|
||||||
|
.wd-picker__error-message,
|
||||||
|
.wd-cell__error-message {
|
||||||
|
text-align: right !important;
|
||||||
|
}
|
||||||
|
.wd-input.is-cell{
|
||||||
|
padding-left: 5px !important;
|
||||||
|
padding-right: 5px !important;
|
||||||
|
}
|
||||||
|
.wd-textarea.is-cell {
|
||||||
|
display: block !important;
|
||||||
|
padding-left: 5px !important;
|
||||||
|
padding-right: 5px !important;
|
||||||
|
}
|
||||||
|
.wd-textarea.is-auto-height uni-textarea {
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
// 错误样式
|
||||||
|
.wd-input.is-error {
|
||||||
|
&::after {
|
||||||
|
background: red !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-input__icon {
|
||||||
|
color: red !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 顶部对齐表单
|
||||||
|
.label-position-top {
|
||||||
|
.wd-input {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.wd-input__body {
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
.wd-input__value {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
padding: 2px 10px;
|
||||||
|
}
|
||||||
|
.wd-input__error-message {
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cell 样式
|
||||||
|
.wd-cell-group {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
.wd-cell {
|
||||||
|
padding-left: 0 !important;
|
||||||
|
}
|
||||||
|
.wd-cell .wd-cell__wrapper {
|
||||||
|
padding-left: 5px !important;
|
||||||
|
padding-right: 5px !important;
|
||||||
|
}
|
||||||
|
.wd-cell__arrow-right {
|
||||||
|
margin-right: -5px;
|
||||||
|
}
|
||||||
|
.wd-picker.is-border .wd-picker__cell::after,
|
||||||
|
.wd-input.is-cell.is-border::after,
|
||||||
|
.wd-textarea.is-cell.is-border::after{
|
||||||
|
left: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// picker
|
||||||
|
.wd-picker__cell {
|
||||||
|
padding-left: 5px !important;
|
||||||
|
padding-right: 5px !important;
|
||||||
|
}
|
||||||
|
.wd-picker__arrow {
|
||||||
|
font-size: 18px !important;
|
||||||
|
margin-right: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// popup
|
||||||
|
// .wd-popup {
|
||||||
|
// z-index: 9999 !important;
|
||||||
|
// }
|
||||||
|
// .wd-overlay {
|
||||||
|
// z-index: 9998 !important;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// uni-ui form
|
||||||
|
// .uni-forms-item {
|
||||||
|
// margin-bottom: 10px !important;
|
||||||
|
// }
|
||||||
|
// .uni-forms-item__label {
|
||||||
|
// height: auto !important;
|
||||||
|
// padding-bottom: 0 !important;
|
||||||
|
// font-size: var(--wot-fs-content) !important;
|
||||||
|
// }
|
||||||
|
// .uni-easyinput__content-input {
|
||||||
|
// height: 30px !important;
|
||||||
|
// }
|
||||||
|
// .uni-easyinput__placeholder-class {
|
||||||
|
// font-size: var(--wot-fs-content) !important;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // tag
|
||||||
|
// .wd-tag {
|
||||||
|
// font-size: var(--wot-fs-secondary) !important;
|
||||||
|
// border-radius: 4px !important;
|
||||||
|
// }
|
||||||
@@ -57,3 +57,79 @@ export interface IForgetPasswordForm {
|
|||||||
password: string
|
password: string
|
||||||
confirmPassword: string
|
confirmPassword: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VIP信息接口
|
||||||
|
*/
|
||||||
|
export interface IVipInfo {
|
||||||
|
id: number
|
||||||
|
endTime: string
|
||||||
|
vipType: number
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单接口
|
||||||
|
*/
|
||||||
|
export interface IOrder {
|
||||||
|
id: number
|
||||||
|
orderSn: string
|
||||||
|
bookEntity: {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
images: string
|
||||||
|
}
|
||||||
|
orderMoney: number
|
||||||
|
paymentMethod: string // '4'-虚拟货币, '5'-真实货币
|
||||||
|
createTime: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VIP套餐接口
|
||||||
|
*/
|
||||||
|
export interface IVipPackage {
|
||||||
|
id: number
|
||||||
|
dictType: string // 价格
|
||||||
|
dictValue: string // 产品ID
|
||||||
|
money: number
|
||||||
|
priceTypeId: number
|
||||||
|
remark: string // 时长(天数)
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交易记录接口
|
||||||
|
*/
|
||||||
|
export interface ITransaction {
|
||||||
|
id: number
|
||||||
|
orderType: string // '充值' | '消费'
|
||||||
|
changeAmount: number
|
||||||
|
remark: string
|
||||||
|
createTime: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反馈表单接口
|
||||||
|
*/
|
||||||
|
export interface IFeedbackForm {
|
||||||
|
type: string // 问题类型
|
||||||
|
account: string // 账号
|
||||||
|
relation?: string // 订单编号(可选)
|
||||||
|
content: string // 问题描述
|
||||||
|
contactInformation: string // 联系电话
|
||||||
|
image?: string // 截图(多张用逗号分隔)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页数据接口
|
||||||
|
*/
|
||||||
|
export interface IPageData<T> {
|
||||||
|
records: T[]
|
||||||
|
total: number
|
||||||
|
size: number
|
||||||
|
current: number
|
||||||
|
pages: number
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* 页面跳转
|
||||||
|
*/
|
||||||
export const onPageJump = (path: string) => {
|
export const onPageJump = (path: string) => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: path
|
url: path
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拨打电话
|
||||||
|
*/
|
||||||
|
export const makePhoneCall = (phoneNumber: string, title: string, t: Function) => {
|
||||||
|
uni.showModal({
|
||||||
|
title: title,
|
||||||
|
content: phoneNumber,
|
||||||
|
confirmText: t('common.confirm'),
|
||||||
|
cancelText: t('common.cancel'),
|
||||||
|
success: (res: any) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.makePhoneCall({
|
||||||
|
phoneNumber: phoneNumber,
|
||||||
|
success: () => {
|
||||||
|
console.log('拨打电话成功')
|
||||||
|
},
|
||||||
|
fail: (error: any) => {
|
||||||
|
console.error('拨打电话失败:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制到剪贴板
|
||||||
|
*/
|
||||||
|
export const copyToClipboard = (content: string, title: string, t: Function) => {
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: content,
|
||||||
|
success: () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: title + t('user.copySuccess'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
console.error('复制失败:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
54
utils/system.ts
Normal file
54
utils/system.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
interface SystemInfo {
|
||||||
|
statusBarHeight?: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MenuButtonRect {
|
||||||
|
top: number;
|
||||||
|
height: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LeftIcon {
|
||||||
|
left: string;
|
||||||
|
width: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SYSTEM_INFO: SystemInfo = uni.getSystemInfoSync();
|
||||||
|
|
||||||
|
export const getStatusBarHeight = (): number => SYSTEM_INFO.statusBarHeight || 0;
|
||||||
|
|
||||||
|
export const getTitleBarHeight = (): number => {
|
||||||
|
if (uni.getMenuButtonBoundingClientRect) {
|
||||||
|
const { top, height }: MenuButtonRect = uni.getMenuButtonBoundingClientRect();
|
||||||
|
return height + (top - getStatusBarHeight()) * 2;
|
||||||
|
} else {
|
||||||
|
return 44;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getNavBarHeight = (): number => getStatusBarHeight() + getTitleBarHeight();
|
||||||
|
|
||||||
|
export const getLeftIconLeft = (): number => {
|
||||||
|
// #ifdef MP-TOUTIAO
|
||||||
|
const { leftIcon: { left, width } }: { leftIcon: LeftIcon } = tt.getCustomButtonBoundingClientRect();
|
||||||
|
return left + parseInt(width);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-TOUTIAO
|
||||||
|
return 0;
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getNavbarHeight2 = () => {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
statusBarHeight.value = systemInfo.statusBarHeight || 0
|
||||||
|
|
||||||
|
let navBarHeight = 44
|
||||||
|
if (systemInfo.model.indexOf('iPhone') !== -1 && parseInt(systemInfo.model.slice(-2)) >= 11) {
|
||||||
|
navBarHeight = 48
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalHeight = statusBarHeight.value + navBarHeight
|
||||||
|
navbarHeight.value = totalHeight + 'px'
|
||||||
|
}
|
||||||
@@ -30,5 +30,10 @@ child_process.exec(
|
|||||||
})
|
})
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [uni()]
|
plugins: [uni()],
|
||||||
|
define: {
|
||||||
|
__VUE_I18N_FULL_INSTALL__: true,
|
||||||
|
__VUE_I18N_LEGACY_API__: false,
|
||||||
|
__INTLIFY_PROD_DEVTOOLS__: false
|
||||||
|
}
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user