diff --git a/App.vue b/App.vue index 168b57a..8281a62 100644 --- a/App.vue +++ b/App.vue @@ -12,8 +12,9 @@ } - diff --git a/api/modules/user.ts b/api/modules/user.ts new file mode 100644 index 0000000..dabd820 --- /dev/null +++ b/api/modules/user.ts @@ -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>({ + url: 'common/user/getUserInfo', + method: 'POST' + }) + return res +} + +/** + * 获取VIP信息 + */ +export async function getVipInfo() { + const res = await mainClient.request>({ + 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 }>>({ + url: 'bookAbroad/home/getAbroadOrderList', + method: 'POST', + data: { current, limit } + }) + return res +} + +/** + * 获取VIP套餐列表 + */ +export async function getVipPackages() { + const res = await mainClient.request>({ + url: 'book/sysdictdata/getData', + method: 'POST', + data: { dictLabel: 'googleVip' } + }) + return res +} + +/** + * 创建订单 + * @param data 订单数据 + */ +export async function createOrder(data: any) { + const res = await mainClient.request>({ + url: 'bookAbroad/order/placeOrder', + method: 'POST', + data + }) + return res +} + +/** + * 获取交易记录列表 + * @param userId 用户ID + */ +export async function getTransactionList(userId: number) { + const res = await mainClient.request>({ + url: 'common/transactionDetails/getTransactionDetailsList', + method: 'POST', + data: { userId } + }) + return res +} + +/** + * 更新用户基本信息 + * @param data 用户信息 + */ +export async function updateUserInfo(data: Partial) { + const res = await mainClient.request({ + 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({ + 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({ + 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({ + 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 { + 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({ + 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({ + 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({ + url: 'Ipa/veri', + method: 'POST', + data + }) + return res +} diff --git a/components/nav-bar/nav-bar.vue b/components/nav-bar/nav-bar.vue new file mode 100644 index 0000000..c0c3de0 --- /dev/null +++ b/components/nav-bar/nav-bar.vue @@ -0,0 +1,40 @@ + + + + + \ No newline at end of file diff --git a/locale/en.json b/locale/en.json index d610f45..d5b973c 100644 --- a/locale/en.json +++ b/locale/en.json @@ -73,5 +73,120 @@ "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.", "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" } } diff --git a/locale/zh-Hans.json b/locale/zh-Hans.json index a706a62..e88b185 100644 --- a/locale/zh-Hans.json +++ b/locale/zh-Hans.json @@ -74,5 +74,120 @@ "passwordStrengthMedium": "密码强度中等", "passwordStrengthWeak": "请使用至少包含大小写字母、数字、符号中的两种类型,长度为8个字符的密码", "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": "请选择套餐" } } diff --git a/pages.json b/pages.json index a4155de..5d3b9c5 100644 --- a/pages.json +++ b/pages.json @@ -25,6 +25,54 @@ "navigationBarBackgroundColor": "#FFFFFF", "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": { @@ -61,7 +109,7 @@ "text": "%tabar.book%" }, { - "pagePath": "pages/my/my", + "pagePath": "pages/user/index", "iconPath": "static/tab/icon4_n.png", "selectedIconPath": "static/tab/icon4_y.png", "text": "%tabar.user%" diff --git a/pages/index/index.vue b/pages/index/index.vue index 3227bfe..47ad80d 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -1,6 +1,6 @@ diff --git a/pages/login/login.vue b/pages/login/login.vue index 04cf85e..e7a5057 100644 --- a/pages/login/login.vue +++ b/pages/login/login.vue @@ -99,9 +99,9 @@ - - 登录遇到问题?去反馈 - + @@ -240,7 +240,6 @@ const isCodeEmpty = () => { } // 邮箱格式验证 const isEmailVerified = (email: string) => { - console.log(email, validateEmail(email)) if (!validateEmail(email)) { uni.showToast({ title: t('login.emailError'), diff --git a/pages/user/about/index.vue b/pages/user/about/index.vue new file mode 100644 index 0000000..af16d6f --- /dev/null +++ b/pages/user/about/index.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/pages/user/feedback/index.vue b/pages/user/feedback/index.vue new file mode 100644 index 0000000..9d75852 --- /dev/null +++ b/pages/user/feedback/index.vue @@ -0,0 +1,363 @@ + + + + + diff --git a/pages/user/index.vue b/pages/user/index.vue new file mode 100644 index 0000000..09129ce --- /dev/null +++ b/pages/user/index.vue @@ -0,0 +1,334 @@ + + + + + diff --git a/pages/user/order/index.vue b/pages/user/order/index.vue new file mode 100644 index 0000000..f81bb3c --- /dev/null +++ b/pages/user/order/index.vue @@ -0,0 +1,309 @@ + + + + + diff --git a/pages/user/profile/index.vue b/pages/user/profile/index.vue new file mode 100644 index 0000000..922eba8 --- /dev/null +++ b/pages/user/profile/index.vue @@ -0,0 +1,645 @@ + + + + + diff --git a/pages/user/settings/index.vue b/pages/user/settings/index.vue new file mode 100644 index 0000000..60c80ac --- /dev/null +++ b/pages/user/settings/index.vue @@ -0,0 +1,340 @@ + + + + + diff --git a/pages/user/wallet/index.vue b/pages/user/wallet/index.vue new file mode 100644 index 0000000..3c517b8 --- /dev/null +++ b/pages/user/wallet/index.vue @@ -0,0 +1,523 @@ + + + + + diff --git a/pages/user/wallet/list.vue b/pages/user/wallet/list.vue new file mode 100644 index 0000000..eb5ab6b --- /dev/null +++ b/pages/user/wallet/list.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/static/tailwind.css b/static/tailwind.css index 0ae3471..72f8032 100644 --- a/static/tailwind.css +++ b/static/tailwind.css @@ -8,7 +8,6 @@ --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; @@ -187,9 +186,6 @@ .sticky { position: sticky; } - .isolate { - isolation: isolate; - } .container { width: 100%; @media (width >= 40rem) { @@ -211,9 +207,6 @@ .block { display: block; } - .contents { - display: contents; - } .flex { display: flex; } @@ -229,12 +222,15 @@ .inline-block { display: inline-block; } - .list-item { - display: list-item; - } .table { display: table; } + .w-\[100px\] { + width: 100px; + } + .flex-1 { + flex: 1; + } .flex-shrink { flex-shrink: 1; } @@ -244,6 +240,9 @@ .resize { resize: both; } + .flex-wrap { + flex-wrap: wrap; + } .border { border-style: var(--tw-border-style); border-width: 1px; @@ -251,35 +250,25 @@ .bg-\[red\] { background-color: red; } + .text-center { + text-align: center; + } .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); @@ -304,10 +293,6 @@ --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) { diff --git a/stores/user.ts b/stores/user.ts index 57c7dfc..dcda25e 100644 --- a/stores/user.ts +++ b/stores/user.ts @@ -4,20 +4,22 @@ import { setAuthToken, clearAuthToken } from '@/utils/auth' import type { IUserInfo } from '@/types/user' export const useUserStore = defineStore('user', { - state: (): IUserInfo => ({ + state: (): IUserInfo => (uni.getStorageSync('userInfo') || { id: 0, name: '', + nickname: '', avatar: '', email: '', - phone: '', - token: uni.getStorageSync('token') || '', + token: '', + age: '', + tel: '', + sex: '', }), getters: { isLoggedIn: (state: any) => Boolean(state.token), userInfo: (state: any) => { - const { id, name, avatar, email, phone } = state - return { id, name, avatar, email, phone } + return state }, }, @@ -54,7 +56,6 @@ export const useUserStore = defineStore('user', { clearAuthToken() uni.removeStorageSync('userInfo') - uni.reLaunch({ url: '/pages/user/login' }) }, /** 从本地存储恢复用户信息 */ diff --git a/style/tailwind.css b/style/tailwind.css new file mode 100644 index 0000000..0ae3471 --- /dev/null +++ b/style/tailwind.css @@ -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: ""; + 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: ""; + 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: ""; + 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: ""; + 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; + } + } +} diff --git a/style/ui.scss b/style/ui.scss new file mode 100644 index 0000000..366c063 --- /dev/null +++ b/style/ui.scss @@ -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; +// } \ No newline at end of file diff --git a/types/user.ts b/types/user.ts index 39cad71..e319a5e 100644 --- a/types/user.ts +++ b/types/user.ts @@ -57,3 +57,79 @@ export interface IForgetPasswordForm { password: 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 { + records: T[] + total: number + size: number + current: number + pages: number + [key: string]: any +} diff --git a/utils/index.ts b/utils/index.ts index b2be9bb..1f3e68b 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -1,5 +1,51 @@ +/** + * 页面跳转 + */ export const onPageJump = (path: string) => { uni.navigateTo({ 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) + } + }) } \ No newline at end of file diff --git a/utils/system.ts b/utils/system.ts new file mode 100644 index 0000000..a7602e3 --- /dev/null +++ b/utils/system.ts @@ -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' +} \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index b5a96ac..e7c1b3a 100644 --- a/vite.config.js +++ b/vite.config.js @@ -30,5 +30,10 @@ child_process.exec( }) export default defineConfig({ - plugins: [uni()] + plugins: [uni()], + define: { + __VUE_I18N_FULL_INSTALL__: true, + __VUE_I18N_LEGACY_API__: false, + __INTLIFY_PROD_DEVTOOLS__: false + } }) \ No newline at end of file