20 Commits

Author SHA1 Message Date
a04d40a6d0 修复:电子书VIP套餐页“白块”问题 2025-12-19 14:38:17 +08:00
11dfd01b39 Merge branch 'main' of https://git.nuttyreading.com/zm/taimed-international-app 2025-12-19 13:59:34 +08:00
ce12470688 优化:数据迁移功能优化 2025-12-19 13:59:30 +08:00
62adfa1d4f Merge branch 'main' of https://git.nuttyreading.com/zm/taimed-international-app 2025-12-19 08:57:37 +08:00
778c6372e7 修复:后台充值和积分,app不显示订单号和跳转详情 2025-12-19 08:57:17 +08:00
54815f871f 修复:解决控制台报错 2025-12-18 16:01:09 +08:00
cf9ef2e6dd Merge branch 'main' of https://git.nuttyreading.com/zm/taimed-international-app 2025-12-18 15:57:54 +08:00
2b0e339bc9 修复:完善数据迁移;修改logo及课程首页接口文件覆盖问题; 2025-12-18 15:57:50 +08:00
4df53c611b 修改:后台操作,充值、积分功能。手机端进行限制 2025-12-18 15:30:23 +08:00
56772970e2 修复:优化访客模式 2025-12-18 14:03:01 +08:00
9e04533bdc 更新:更新打包版本 2025-12-18 09:20:51 +08:00
77fedbe877 修复:ios审核上传照片问题 2025-12-17 18:26:21 +08:00
6c2811646a 修复:修改logo图 2025-12-15 15:47:16 +08:00
9e00409e5e 修复:不同系统支付,显示支付方式不同 2025-12-15 11:18:22 +08:00
1457a24cea 修复:更新ios支付接口 2025-12-15 10:41:41 +08:00
34d6cfdf9e 更新:访客模式可以查看图书首页 2025-12-12 14:33:47 +08:00
04e2196942 优化:去掉不必要的loading 2025-12-12 11:57:43 +08:00
6e5d63febe 更新:课程首页增加骨架屏 2025-12-12 10:22:44 +08:00
3ce5e07573 Merge branch 'main' of https://git.nuttyreading.com/zm/taimed-international-app 2025-12-11 17:30:53 +08:00
d98e1ef024 更新:ios支付 2025-12-11 17:30:45 +08:00
33 changed files with 732 additions and 437 deletions

22
App.vue
View File

@@ -3,31 +3,9 @@
import update from "@/uni_modules/uni-upgrade-center-app/utils/check-update";
// #endif
import { useUserStore } from '@/stores/user'
export default {
onLaunch: function() {
const userStore = useUserStore()
console.log('App Launch')
// 保存原生 switchTab 方法
const originalSwitchTab = uni.switchTab;
uni.switchTab = (options) => {
if (options.url.includes('/pages/book/index') && !userStore.token) {
uni.showModal({
title: '提示',
content: '请先登录后访问该页面',
confirmText: '去登录',
success: (res) => {
console.log(res, 'res');
if (res.confirm) uni.navigateTo({
url: '/pages/login/login'
});
}
});
return; // 拦截跳转
}
// 已登录/非拦截页 → 执行原生跳转
originalSwitchTab.call(uni, options);
}
// 检测自动更新
// #ifdef APP-PLUS
update();

View File

@@ -8,7 +8,7 @@ export const ENV = process.env.NODE_ENV || 'development';
const BASE_URL_MAP = {
development: {
MAIN: 'http://192.168.110.100:9300/pb/', // 张川川
//MAIN: 'https://global.nuttyreading.com/', // 线上
// MAIN: 'https://global.nuttyreading.com/', // 线上
// PAYMENT: 'https://dev-pay.example.com', // 暂时用不到
// CDN: 'https://cdn-dev.example.com', // 暂时用不到
},

View File

@@ -8,7 +8,7 @@ import type { ILoginResponse } from '@/types/user'
* @param code 验证码
*/
export async function loginWithCode(tel: string, code: string) {
const res = await mainClient.request<IApiResponse<ILoginResponse>>({
const res = await mainClient.request<ILoginResponse>({
url: 'book/user/registerOrLogin',
method: 'GET',
data: { tel, code }
@@ -22,7 +22,7 @@ export async function loginWithCode(tel: string, code: string) {
* @param password 密码
*/
export async function loginWithPassword(phone: string, password: string) {
const res = await mainClient.request<IApiResponse<ILoginResponse>>({
const res = await mainClient.request<ILoginResponse>({
url: 'book/user/login',
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },

View File

@@ -42,7 +42,7 @@ export const bookHomeApi = {
*/
getRecommendBooks() {
return skeletonClient.request<IRecommendBooksResponse>({
url: 'bookAbroad/home/getRecommendBooks',
url: uni.getStorageSync('token') ? 'bookAbroad/home/getRecommendBooks' : 'visitor/bookAbroad/getRecommendBooks',
method: 'POST',
data: {}
})
@@ -54,7 +54,7 @@ export const bookHomeApi = {
*/
getBookLabelList(type: number) {
return skeletonClient.request<ILabelListResponse>({
url: 'bookAbroad/home/getBookAbroadLableList',
url: uni.getStorageSync('token') ? 'bookAbroad/home/getBookAbroadLableList' : 'visitor/bookAbroad//getBookAbroadLableList',
method: 'POST',
data: { type }
})
@@ -66,7 +66,7 @@ export const bookHomeApi = {
*/
getSubLabelList(pid: number) {
return skeletonClient.request<ILabelListResponse>({
url: 'bookAbroad/home/getBookAbroadLableListByPid',
url: uni.getStorageSync('token') ? 'bookAbroad/home/getBookAbroadLableListByPid' : 'visitor/bookAbroad//getBookAbroadLableListByPid',
method: 'POST',
data: { pid }
})
@@ -78,7 +78,7 @@ export const bookHomeApi = {
*/
getBooksByLabel(lableId: number) {
return skeletonClient.request<IBookListResponse>({
url: 'bookAbroad/home/getAbroadBookListByLable',
url: uni.getStorageSync('token') ? 'bookAbroad/home/getAbroadBookListByLable' : 'visitor/bookAbroad/getAbroadBookListByLable',
method: 'POST',
data: { lableId }
})

View File

@@ -1,10 +1,7 @@
// api/modules/common.ts
import { mainClient } from '@/api/clients/main'
import { mainClient, skeletonClient } from '@/api/clients'
import type { IApiResponse } from '@/api/types'
import type { IAgreement } from '@/types/user'
import { useUserStore } from '@/stores/user'
export const commonApi = {
/**
@@ -24,7 +21,7 @@ export const commonApi = {
* @param id 协议 ID (111: 用户协议, 112: 隐私政策)
*/
getAgreement: async (id: number) => {
const res = await mainClient.request<IApiResponse<IAgreement>>({
const res = await skeletonClient.request<IApiResponse<IAgreement>>({
url: 'sys/agreement/getAgreement',
method: 'POST',
data: { id }
@@ -39,9 +36,8 @@ export const commonApi = {
* @returns 消息列表
*/
getMessageList(isBook: number, isMedical: number, isSociology: number) {
const userStore = useUserStore()
return mainClient.request<IMessageListResponse>({
url: userStore.token ? 'common/message/listByPage' : '/visitor/listByPage',
return skeletonClient.request<IApiResponse>({
url: uni.getStorageSync('token') ? 'common/message/listByPage' : '/visitor/listByPage',
method: 'POST',
data: { isBook, isMedical, isSociology }
})

View File

@@ -13,7 +13,6 @@ import type {
} from '@/types/course'
import type { ISearchRequest, ISearchResponse } from '@/types/search'
import type { ICommentListResponse, IAddCommentResponse, IComment } from '@/types/comment'
import { useUserStore } from '@/stores/user'
/**
* 课程相关API
@@ -36,7 +35,7 @@ export const courseApi = {
* @returns 观看记录列表
*/
getUserLateCourseList() {
return mainClient.request<IUserLateCourseListResponse>({
return skeletonClient.request<IUserLateCourseListResponse>({
url: 'medical/home/getUserLateCourseList',
method: 'POST',
data: {}
@@ -55,9 +54,8 @@ export const courseApi = {
page: number,
limit: number
}) {
const userStore = useUserStore()
return mainClient.request<IMarketCourseListResponse>({
url: userStore.token ? 'medical/home/getMarketCourseList' : 'visitor/getMarketCourseList',
return skeletonClient.request<IMarketCourseListResponse>({
url: uni.getStorageSync('token') ? 'medical/home/getMarketCourseList' : 'visitor/getMarketCourseList',
method: 'POST',
data
})

View File

@@ -1,16 +1,9 @@
// api/modules/course.ts
import { createRequestClient } from '../request'
import { SERVICE_MAP } from '../config'
import { mainClient, skeletonClient } from '@/api/clients'
import type {
ICourseCategoryResponse,
IUserLateCourseListResponse,
IMarketCourseListResponse,
ICourseMedicalLabelsResponse
} from '@/types/course'
import { useUserStore } from '@/stores/user'
const client = createRequestClient({ baseURL: SERVICE_MAP.MAIN })
/**
* 课程分类及分类下课程相关API
@@ -22,9 +15,8 @@ export const courseSubjectClassificationApi = {
* @returns 分类数据
*/
getCourseMedicalTree() {
const userStore = useUserStore()
return client.request<ICourseCategoryResponse>({
url: userStore.token ? 'medical/home/getCourseMedicalTree' : '/visitor/getCourseMedicalTree',
return skeletonClient.request<ICourseCategoryResponse>({
url: uni.getStorageSync('token') ? 'medical/home/getCourseMedicalTree' : '/visitor/getCourseMedicalTree',
method: 'POST',
data: {}
})
@@ -36,7 +28,7 @@ export const courseSubjectClassificationApi = {
* @returns 分类数据
*/
getCourseSoulTree() {
return client.request<ICourseCategoryResponse>({
return skeletonClient.request<ICourseCategoryResponse>({
url: 'psyche/home/getPsycheLabels',
method: 'POST',
data: { id: 0 }
@@ -49,7 +41,7 @@ export const courseSubjectClassificationApi = {
* @returns 分类数据
*/
getCourseSociologyTree() {
return client.request<ICourseCategoryResponse>({
return skeletonClient.request<ICourseCategoryResponse>({
url: 'sociology/home/getSociologyLabels',
method: 'POST',
data: { id: 0 }
@@ -62,7 +54,7 @@ export const courseSubjectClassificationApi = {
* @returns 分类数据
*/
getCourseMedicalLabels(id: number) {
return client.request<ICourseCategoryResponse>({
return mainClient.request<ICourseCategoryResponse>({
url: 'medical/home/getMedicalLabels',
method: 'POST',
data: { id }
@@ -75,7 +67,7 @@ export const courseSubjectClassificationApi = {
* @returns 分类数据
*/
getCourseMedicalChildLabels(id: number) {
return client.request<ICourseCategoryResponse>({
return mainClient.request<ICourseCategoryResponse>({
url: 'medical/home/getChildCourseMedicalTree',
method: 'POST',
data: { id }
@@ -93,7 +85,7 @@ export const courseSubjectClassificationApi = {
page: number, // 页码
limit: number // 每页数量
}) {
return client.request<IMarketCourseListResponse>({
return mainClient.request<IMarketCourseListResponse>({
url: 'medical/home/getMedicalCourseList',
method: 'POST',
data
@@ -106,7 +98,7 @@ export const courseSubjectClassificationApi = {
* @returns 分类数据
*/
getCourseSoulChildLabels(id: number) {
return client.request<ICourseCategoryResponse>({
return mainClient.request<ICourseCategoryResponse>({
url: 'psyche/home/getChildCoursePsycheTree',
method: 'POST',
data: { id }
@@ -124,7 +116,7 @@ export const courseSubjectClassificationApi = {
page: number, // 页码
limit: number // 每页数量
}) {
return client.request<IMarketCourseListResponse>({
return mainClient.request<IMarketCourseListResponse>({
url: 'psyche/home/getPsycheCourseList',
method: 'POST',
data
@@ -142,7 +134,7 @@ export const courseSubjectClassificationApi = {
page: number, // 页码
limit: number // 每页数量
}) {
return client.request<IMarketCourseListResponse>({
return mainClient.request<IMarketCourseListResponse>({
url: 'sociology/course/getSociologyCourseList',
method: 'POST',
data

View File

@@ -1,5 +1,5 @@
// api/modules/user.ts
import { mainClient } from '@/api/clients/main'
import { mainClient, skeletonClient } from '@/api/clients'
import { paymentClient } from '@/api/clients/payment'
import type { IApiResponse } from '@/api/types'
import type {
@@ -17,7 +17,7 @@ import { SERVICE_MAP } from '@/api/config'
* 获取用户信息
*/
export async function getUserInfo() {
const res = await mainClient.request<IApiResponse<{ user: IUserInfo }>>({
const res = await skeletonClient.request<IApiResponse<{ user: IUserInfo }>>({
url: 'common/user/getUserInfo',
method: 'POST'
})
@@ -324,7 +324,7 @@ export async function migrateUserData(data: { tel: string, code: string, type: s
* }
*/
export async function getUserMigrateInfo() {
const res = await mainClient.request<IApiResponse>({
const res = await skeletonClient.request<IApiResponse>({
url: 'common/user/getMigrationList',
method: 'POST',
})
@@ -336,7 +336,7 @@ export async function getUserMigrateInfo() {
* @return
*/
export async function getUserContributionData() {
const res = await mainClient.request<IApiResponse>({
const res = await skeletonClient.request<IApiResponse>({
url: 'common/userContribution/getUserContribution',
method: 'POST'
})
@@ -357,4 +357,22 @@ export async function getUserContributionByTypeList(current : number, limit : nu
data: { current, limit, type, }
})
return res
}
/**
* ios支付
* @param transactionId 支付交易id
* @param productId 商品id
* @param orderId 订单id
* @param receiptData 苹果返回收据
* @param customerOid 用户id
* @return
*/
export async function getIosPayment(transactionId : string, productId : string, orderId : string, receiptData : string, customerOid : string) {
const res = await mainClient.request<IApiResponse>({
url: 'Ipa/veri',
method: 'POST',
data: { transactionId, productId, orderId, receiptData, customerOid}
})
return res
}

View File

@@ -19,6 +19,9 @@ import LineList from './templates/LineList.vue'
import BookCard from './templates/BookCard.vue'
import ImageCard from './templates/ImageCard.vue'
import BookInfo from './templates/BookInfo.vue'
import Menu from './templates/Menu.vue'
// 注册组件
const components = {
@@ -28,6 +31,7 @@ const components = {
'image-card': ImageCard,
'book-card': BookCard,
'book-info': BookInfo,
'menu': Menu
}
type RequestFn = () => Promise<any>

View File

@@ -0,0 +1,33 @@
<template>
<view class="menu-box">
<wd-skeleton :row-col="grid" animation="gradient" />
</view>
</template>
<script lang="ts" setup>
const props = defineProps<{
size?: any[],
count: number
}>()
const list = Array.from({ length: props.count }, (_, index) => (props.size || []))
const grid = [
[
{ width: '96rpx', height: '96rpx' },
{ width: '96rpx', height: '96rpx' },
{ width: '96rpx', height: '96rpx' },
{ width: '96rpx', height: '96rpx' }
]
]
</script>
<style lang="scss" scoped>
.menu-box {
padding: 40rpx 80rpx;
background-color: #f5f5f5;
:deep(.wd-skeleton) {
margin: 0 auto;
}
}
</style>

View File

@@ -145,7 +145,7 @@
"updateFailed": "Update Failed",
"paymentMethod": "Payment Method",
"googlePay": "Google Pay",
"applePay": "Apple Pay",
"applePay": "In-App Purchase",
"agreeText": "I have agreed",
"agreement": "Recharge Agreement",
"agreeFirst": "Please agree to the recharge agreement first",
@@ -223,8 +223,8 @@
"migrateCodePlaceholder": "The code obtained from chinese account",
"migrateWarning": "Migration is irreversible, please proceed with caution!",
"migrateInstructions": "Migration Instructions",
"instruction1": "Please obtain the migration code from your chinese account, get it by going to 【我的】-【数据迁移】-【获取迁移验证码】.",
"instruction2": "After data migration is complete, the chinese account data will be cleared, and all purchased Tianyi Coins, points, courses, E-book, VIP, certificate, and User Contribution will be transferred to the current account",
"instruction1": "Please obtain the migration code from your chinese account, get it by going to 【我的】-【数据迁移】-【查看账号和迁移验证码】.",
"instruction2": "After data migration is complete, the chinese account data will be cleared, and all purchased Tianyi Coins, points, courses, VIP and User Hufen will be transferred to the current account",
"instruction3": "The migration process may take a few minutes, please be patient.",
"instruction4": "If you encounter any issues, please contact customer service for assistance.",
"alreadyMigrated": "You have already migrated:",
@@ -234,7 +234,9 @@
"certificate": "My certificate",
"iHufen": "My lake",
"hufenRecord": "Lake division record",
"hufen": "Hufen"
"hufen": "Hufen",
"backEnd" : "Back-end recharge",
"cannotView" : "The recharge in the background cannot be viewed"
},
"book": {
"title": "My Books",

View File

@@ -146,7 +146,7 @@
"updateFailed": "更新失败",
"paymentMethod": "支付方式",
"googlePay": "Google Pay",
"applePay": "Apple Pay",
"applePay": "IAP 支付",
"agreeText": "我已同意",
"agreement": "充值协议",
"agreeFirst": "请先同意充值协议",
@@ -224,8 +224,8 @@
"migrateCodePlaceholder": "国内版账号获取的迁移验证码",
"migrateWarning": "迁移后不可恢复,请谨慎操作!",
"migrateInstructions": "迁移说明",
"instruction1": "请在吴门医述、心灵空间、众妙之门、疯子读书任意APP中获取迁移验证码获取方式【我的】-【数据迁移】-【获取迁移验证码】。",
"instruction2": "数据迁移完成后,旧账号数据将被清空,已购买的天医币、积分、课程、电子书、VIP、证书、湖分将转移到当前账号。",
"instruction1": "请在吴门医述APP中获取迁移验证码获取方式【我的】-【数据迁移】-【查看账号和迁移验证码】。",
"instruction2": "数据迁移完成后,旧账号数据将被清空,已购买的天医币、积分、课程、VIP、湖分将转移到当前账号。",
"instruction3": "迁移过程可能需要几分钟时间,请耐心等待。",
"instruction4": "如遇到问题,请联系客服获取帮助。",
"alreadyMigrated": "您已迁移过:",
@@ -235,7 +235,9 @@
"certificate": "我的证书",
"iHufen": "我的湖分",
"hufenRecord": "湖分记录",
"hufen": "湖分"
"hufen": "湖分",
"backEnd" : "后台充值",
"cannotView" : "后台充值无法查看"
},
"book": {
"title": "我的书单",

View File

@@ -2,8 +2,8 @@
"name" : "吴门国际",
"appid" : "__UNI__1250B39",
"description" : "吴门国际",
"versionName" : "1.0.9",
"versionCode" : 109,
"versionName" : "1.1.1",
"versionCode" : 111,
"transformPx" : false,
/* 5+App */
"app-plus" : {
@@ -113,7 +113,7 @@
"platforms" : "Android",
"url" : "https://ext.dcloud.net.cn/plugin?id=12608",
"android_package_name" : "com.amazinglimited",
"ios_bundle_id" : "",
"ios_bundle_id" : "com.amazinglimited",
"isCloud" : true,
"bought" : 1,
"pid" : "12608",

View File

@@ -12,7 +12,7 @@
/>
<view class="icon-hua">
<image
src="../../static/home_icon.png"
src="../../static/book/home_icon.png"
mode="aspectFit"
class="icon-hua-img"
/>
@@ -35,16 +35,16 @@
<view class="mine-1">
<text class="mine-title">{{ $t('bookHome.block1') }}</text>
<view
v-if="data.page.records.length > 0"
v-if="data?.page?.records.length > 0"
class="mine-more"
@click="handleMoreClick"
>
{{ $t('bookHome.more') }}
<image src="@/static/icon/icon_right.png" />
</view>
<view v-if="data.page.records.length > 0" class="mine-1-list">
<view v-if="data?.page?.records.length > 0" class="mine-1-list">
<view
v-for="(item, index) in data.page.records"
v-for="(item, index) in data?.page?.records"
:key="index"
class="mine-item"
@click="handleMyBookClick(item.id)"
@@ -284,7 +284,7 @@ const vipInfo = computed(() => userStore.userInfo?.userEbookVip?.[0] || null)
/**
* 获取我的书单
*/
const getMyBooks = () => bookHomeApi.getMyBooks(1, 10)
const getMyBooks = () => uni.getStorageSync('token') ? bookHomeApi.getMyBooks(1, 10) : []
/**
* 获取推荐图书
@@ -364,6 +364,8 @@ const handleMyBookClick = (bookId: number) => {
* 处理图书点击
*/
const handleBookClick = (bookId: number) => {
getPrompt()
if(!uni.getStorageSync('token')) return
uni.navigateTo({
url: `/pages/book/detail?id=${bookId}`
})
@@ -382,6 +384,8 @@ const handleMoreClick = () => {
* 处理活动标签点击
*/
const handleActivityLabelClick = async (labelId: number, index: number) => {
getPrompt()
if(!uni.getStorageSync('token')) return
currentActivityIndex.value = index
activityBooksSkeleton.value.reload()
}
@@ -404,6 +408,25 @@ const handleCategoryLevel2Click = async (labelId: number, index: number) => {
categoryBooksSkeleton.value.reload()
}
/**
* 登录提示语
*/
const getPrompt = () => {
if(!uni.getStorageSync('token')) {
uni.showModal({
title: '提示',
content: '请先登录后访问该页面',
confirmText: '去登录',
success: (res) => {
console.log(res , 'res');
if (res.confirm) uni.navigateTo({
url: '/pages/login/login'
});
}
});
}
}
/**
* 页面显示
*/

View File

@@ -31,80 +31,87 @@
>{{ item }}</view>
</view>
<!-- 医学 -->
<view v-if="selectedFirstLevel === '医学'" class="newLeve2">
<view class="home_nar nomargin" style="padding: 0; background-color: #fff">
<view class="flexbox">
<Skeleton
v-if="selectedFirstLevel === '医学'"
ref="medicineMenuSkeletonRef"
theme="menu"
:request="getMedicineMenus"
@success="getMedicineMenusSuccess"
>
<template #content>
<view v-if="selectedFirstLevel === '医学'" class="newLeve2">
<view class="home_nar nomargin" style="padding: 0; background-color: #fff">
<view class="flexbox">
<view
:class="['hn_cl_tit', currentIndex == index ? 'active' : '']"
@click="curseClick(item, index)"
v-for="(item, index) in curseTagList"
:key="item.id"
>
<image :src="item.icon" mode="aspectFit"></image>
<text>{{ item.title }}</text>
</view>
</view>
</view>
<view
:class="['hn_cl_tit', currentIndex == index ? 'active' : '']"
@click="curseClick(item, index)"
v-for="(item, index) in curseTagList"
:key="item.id"
class="fourBox"
style="padding: 0; padding-bottom: 8rpx"
v-if="sbuMedicalTagsList?.length > 0"
>
<image :src="item.icon" mode="aspectFit"></image>
<text>{{ item.title }}</text>
<view
class="childrenBox fourIcon flexbox"
style="justify-content: space-around"
>
<view
class="item flexbox"
@click="curseClickJump(item)"
v-for="(item, index) in sbuMedicalTagsList"
:key="item.id"
>
<image
:src="item.icon"
mode="aspectFit"
v-if="item.icon != '' && item.icon != null"
></image>
<text>{{ item.title }}</text>
</view>
</view>
</view>
</view>
</view>
<view
class="fourBox"
style="padding: 0; padding-bottom: 8rpx"
v-if="sbuMedicalTagsList?.length > 0"
>
</template>
</Skeleton>
<!-- 心理学和国学 -->
<Skeleton
v-if="selectedFirstLevel === '心理学' || selectedFirstLevel === '国学'"
ref="menuSkeletonRef"
theme="menu"
:request="getMenus"
>
<template #content="{ data }">
<view
class="childrenBox fourIcon flexbox"
style="justify-content: space-around"
v-if="data.labels.length > 0"
:class="[
{'sociology_cate_box': selectedFirstLevel === '国学'},
{'soul_cate_box': selectedFirstLevel === '心理学'}
]"
>
<view
class="item flexbox"
@click="curseClickJump(item)"
v-for="(item, index) in sbuMedicalTagsList"
:key="item.id"
>
<image
:src="item.icon"
mode="aspectFit"
v-if="item.icon != '' && item.icon != null"
></image>
<text>{{ item.title }}</text>
</view>
</view>
</view>
</view>
<!-- 心理学 -->
<view v-if="selectedFirstLevel === '心理学'" class="soul_cate_box">
<scroll-view scroll-x="true">
<view class="cate_list" v-if="soulCateList.length > 0">
<view
class="cate_item_box"
v-for="(item, index) in soulCateList"
:key="index"
v-for="item in data.labels"
@click="curseClickJump(item)"
>
<view class="cate_item_border">
<image :src="item.icon"></image>
<image
:src="item.icon"
mode="aspectFill"
style="width: 49rpx; height: 49rpx"
></image>
</view>
<view class="cate_item_name">{{ item.title }}</view>
</view>
</view>
</scroll-view>
</view>
<!-- 国学 -->
<view v-if="selectedFirstLevel === '国学' && sociologyCateList.length > 0" class="sociology_cate_box">
<view
class="cate_item_box"
v-for="(v, i) in sociologyCateList"
@click="curseClickJump(v)"
>
<view class="cate_item_border">
<image
:src="v.icon"
mode="aspectFill"
style="width: 49rpx; height: 49rpx"
></image>
</view>
<view class="cate_item_name">{{ v.title }}</view>
</view>
</view>
</template>
</Skeleton>
<!-- 观看记录区域 -->
<view class="learnBox" v-if="learnList.length > 0">
@@ -143,7 +150,7 @@
<view class="newscoll">
<swiper
class="swiper"
interval="5000"
:interval="5000"
circular
autoplay
vertical
@@ -163,58 +170,66 @@
</view>
<!-- 精彩试听区域 -->
<view class="learnBox" v-if="tryListenList.length > 0">
<view class="learnBox">
<view class="titleBox flexbox">
<image src="../../static/course/try_listen.png" mode="aspectFit"></image>
<text>{{ $t('courseHome.tryListen') }}</text>
</view>
<view class="learn flexbox shiting">
<view
class="item"
v-for="(item, index) in tryListenList"
:key="item.id"
@click="onPageJump('/pages/course/details/course', item.id, item.title)"
>
<view class="imgcontainer">
<image
v-if="item.image == '' || !item.image"
src="../../static/course/nobg.jpg"
mode="aspectFit"
<Skeleton
theme="image-card"
class=""
:size="Array(2).fill({ width: '48%', height: '260rpx' })"
:count="3"
:request="getTryListenList"
>
<template #content="{ data }">
<view v-if="data.courseList.records.length > 0" class="learn flexbox shiting">
<view
class="item"
v-for="(item, index) in data.courseList.records"
:key="item.id"
@click="onPageJump('/pages/course/details/course', item.id, item.title)"
>
</image>
<image v-else :src="item.image"></image>
</view>
<view class="buyItems flexbox">
<view class="txt555">
{{ item.title }}
</view>
<view class="buybtn">
<span>{{ $t('courseHome.buy') }}</span>
<view class="imgcontainer">
<image
v-if="item.image == '' || !item.image"
src="../../static/course/nobg.jpg"
mode="aspectFit"
>
</image>
<image v-else :src="item.image"></image>
</view>
<view class="buyItems flexbox">
<view class="txt555">
{{ item.title }}
</view>
<view class="buybtn">
<span>{{ $t('courseHome.buy') }}</span>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="moreBox shiting">
<text @click="onPageJump('/pages/course/list/tryListen', 1, $t('courseHome.tryListen'))">{{ $t('courseHome.moreTryListen') }}</text>
</view>
<view class="moreBox shiting">
<text @click="onPageJump('/pages/course/list/tryListen', 1, $t('courseHome.tryListen'))">{{ $t('courseHome.moreTryListen') }}</text>
</view>
</template>
</Skeleton>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { onShow, onHide, onPullDownRefresh, onPageScroll, onLoad } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import { onShow, onHide, onPullDownRefresh, onPageScroll } from '@dcloudio/uni-app'
import { courseApi } from '@/api/modules/course'
import { courseSubjectClassificationApi } from '@/api/modules/cousre_subject_classification'
import { commonApi } from '@/api/modules/common'
import { getNotchHeight } from '@/utils/system'
// import { onPageJump } from '@/utils'
import type { IMedicalTag, ICourse, INews } from '@/types/course'
import type { ICategory, ICourse, INews } from '@/types/course'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const { t } = useI18n()
// 系统信息
const scrollTop = ref<number>(0) // 滚动位置
@@ -229,41 +244,25 @@ const handleSearch = ({ value }: { value: string }) => {
}
// 分类相关
const curseTagList = ref<IMedicalTag[]>([]) // 一级分类标签列表
const sbuMedicalTagsList = ref<IMedicalTag[]>([]) // 二级分类标签列表
const curseTagList = ref<ICategory[]>([]) // 一级分类标签列表
const sbuMedicalTagsList = ref<ICategory[]>([]) // 二级分类标签列表
const currentIndex = ref<number>(0) // 当前选中的一级分类索引
const currentItem = ref<IMedicalTag | null>(null) // 当前选中的一级分类项
const currentItem = ref<ICategory | null>(null) // 当前选中的一级分类项
// 学科数据
const firstLevelCategories = ref<string[]>(['医学', '心理学', '国学'])
const selectedFirstLevel = ref<number>('医学') // 当前选中的一级分类索引
/**
* 学科点击处理
*/
const handleFirstLevelClick = (item: string) => {
getPrompt()
if(!userStore.token) return
selectedFirstLevel.value = item
switch (item) {
case '医学':
getMedicalTags()
break
case '心理学':
getSoulCateList()
break
case '国学':
getSociologyCateList()
break
}
}
const selectedFirstLevel = ref('医学') // 当前选中的一级分类索引
/**
* 医学
* 获取课程分类数据
*/
const getMedicalTags = async () => {
const medicineMenuSkeletonRef = ref()
const getMedicineMenus = () => {
sbuMedicalTagsList.value = []
const res = await courseSubjectClassificationApi.getCourseMedicalTree()
return courseSubjectClassificationApi.getCourseMedicalTree()
}
const getMedicineMenusSuccess = (res: any) => {
if (res && res.code === 0) {
if (res.labels && res.labels.length > 0) {
curseTagList.value = res.labels
@@ -286,7 +285,7 @@ const getMedicalTags = async () => {
* 医学
* 一级分类点击处理
*/
const curseClick = (item: IMedicalTag, index: number) => {
const curseClick = (item: ICategory, index: number) => {
currentItem.value = item
currentIndex.value = index
@@ -304,35 +303,54 @@ const curseClick = (item: IMedicalTag, index: number) => {
}
/**
* 心理学
* 菜单
* 获取课程分类数据
*/
const soulCateList = ref<IMedicalTag[]>([])
const getSoulCateList = async () => {
const res = await courseSubjectClassificationApi.getCourseSoulTree()
if (res.labels&&res.labels.length>0) {
soulCateList.value = res.labels;
const menuSkeletonRef = ref()
const getMenus = () => {
switch (selectedFirstLevel.value) {
// case '医学':
// return getMedicalTags()
case '心理学':
return courseSubjectClassificationApi.getCourseSoulTree()
case '国学':
return courseSubjectClassificationApi.getCourseSociologyTree()
}
}
/**
*
* 获取课程分类数据
* 学科点击处理
*/
const sociologyCateList = ref<IMedicalTag[]>([])
const getSociologyCateList = async () => {
const res = await courseSubjectClassificationApi.getCourseSociologyTree()
if (res.labels&&res.labels.length>0) {
sociologyCateList.value = res.labels;
const handleFirstLevelClick = (item: string) => {
getPrompt()
if(!uni.getStorageSync('token')) return
selectedFirstLevel.value = item
if (item === '医学') {
medicineMenuSkeletonRef.value?.reload()
} else {
menuSkeletonRef.value?.reload()
}
// switch (item) {
// case '医学':
// getMedicalTags()
// break
// case '心理学':
// getSoulCateList()
// break
// case '国学':
// getSociologyCateList()
// break
// }
}
/**
* 终极分类点击处理
*/
const curseClickJump = (item: IMedicalTag) => {
const curseClickJump = (item: ICategory) => {
getPrompt()
if(!userStore.token) return
if(!uni.getStorageSync('token')) return
uni.navigateTo({
url: `/pages/course/list/category?id=${item.id}&title=${item.title}&pid=${item.pid}&subject=${selectedFirstLevel.value}`
})
@@ -343,7 +361,7 @@ const curseClickJump = (item: IMedicalTag) => {
*/
const onPageJump = (url: string, id?: number, title?: string) => {
getPrompt()
if(!userStore.token) return
if(!uni.getStorageSync('token')) return
let targetUrl = url
if (id !== undefined) {
targetUrl += `?id=${id}`
@@ -391,7 +409,7 @@ const getNewsList = async () => {
*/
const newsClick = (item: INews) => {
getPrompt()
if(!userStore.token) return
if(!uni.getStorageSync('token')) return
uni.navigateTo({
url: `/pages/news/details?newsId=${item.id}&url=${item.url}&type=${item.type}`
})
@@ -399,30 +417,17 @@ const newsClick = (item: INews) => {
}
// 精彩试听
const tryListenList = ref<ICourse[]>([]) // 试听课程列表
/**
* 获取试听课程列表
*/
const getTryListenList = async () => {
const res = await courseApi.getMarketCourseList({
page: 1,
limit: 6,
id: 1
})
if (res && res.code === 0) {
if (res.courseList && res.courseList.records && res.courseList.records.length > 0) {
tryListenList.value = res.courseList.records
} else {
tryListenList.value = []
}
}
}
const getTryListenList = () => courseApi.getMarketCourseList({ page: 1, limit: 6, id: 1 })
/**
* 登录提示语
*/
const getPrompt = () => {
if(!userStore.token) {
console.log(userStore.token);
if(!uni.getStorageSync('token')) {
uni.showModal({
title: '提示',
content: '请先登录后访问该页面',
@@ -441,10 +446,9 @@ const getTryListenList = async () => {
* 统一请求所有数据
*/
const requestAll = async () => {
if(userStore.token){
if(uni.getStorageSync('token')){
getLearnCourse()
}
getMedicalTags()
}
getTryListenList()
getNewsList()
}
@@ -453,35 +457,36 @@ const requestAll = async () => {
* 页面挂载
*/
onMounted(() => {
if(!userStore.token) {
// 重置分类索引
currentIndex.value = 0
if(!uni.getStorageSync('state') && !uni.getStorageSync('token')) {
uni.navigateTo({
url: '/pages/login/login'
});
}
// 重置分类索引
currentIndex.value = 0
if(uni.getStorageSync('state')) uni.removeStorageSync('state')
// 请求所有数据
requestAll()
console.log('进来了2');
})
/**
* 页面显示
*/
onShow(() => {
console.log('进来了1');
// 检查是否有固定的分类选择状态
const fixed = uni.getStorageSync('fixed')
if (fixed && currentItem.value) {
curseClick(currentItem.value, currentIndex.value)
} else {
currentIndex.value = 0
currentItem.value = null
}
// const fixed = uni.getStorageSync('fixed')
// if (fixed && currentItem.value) {
// curseClick(currentItem.value, currentIndex.value)
// } else {
// currentIndex.value = 0
// currentItem.value = null
// }
// 刷新数据
requestAll()
// requestAll()
if(uni.getStorageSync('token')){
getLearnCourse()
}
})
/**
@@ -504,7 +509,7 @@ onPullDownRefresh(() => {
/**
* 页面滚动
*/
onPageScroll((e) => {
onPageScroll((e: any) => {
scrollTop.value = e.scrollTop
})
</script>
@@ -673,42 +678,47 @@ $border-color: #eeeeee;
box-shadow: 0rpx 0rpx 6rpx 0rpx #f9f6ea;
border-radius: 10rpx;
margin: 0 10rpx;
display: flex;
align-items: center;
justify-content: space-around;
.cate_item_box {
width: 20%;
padding: 40rpx 0 30rpx;
text-align: center;
.cate_list {
display: flex;
align-items: center;
justify-content: space-around;
.cate_item_border {
width: 60rpx;
height: 60rpx;
background-size: 100% 100%;
background-image: url("@/static/icon/cate_bg.png");
border-radius: 4rpx;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
.cate_item_box {
width: 20%;
padding: 40rpx 0 30rpx;
image {
width: 46rpx;
height: 46rpx;
}
}
.cate_item_name {
margin-top: 15rpx;
font-size: 24rpx;
line-height: 34rpx;
text-align: center;
color: #fff;
font-weight: bold;
}
.cate_item_border {
width: 60rpx;
height: 60rpx;
background-size: 100% 100%;
background-image: url("@/static/soul/cate_bg.png");
border-radius: 4rpx;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
image {
width: 46rpx;
height: 46rpx;
}
}
.cate_item_name {
margin-top: 15rpx;
font-size: 24rpx;
line-height: 34rpx;
text-align: center;
color: #fff;
font-weight: bold;
}
.child-menu {
display: none;
background-color: #000;
box-shadow: 0 4rpx 12rpx rgba(51, 97, 165, 0.08);
border-radius: 0 0 20rpx 20rpx;
padding: 10rpx 0;
}
}
}
@@ -742,7 +752,7 @@ $border-color: #eeeeee;
width: 65rpx;
height: 78rpx;
background-size: 100% 100%;
background-image: url("@/static/soul/cate_bg.png");
background-image: url("@/static/icon/cate_bg.png");
border-radius: 4rpx;
display: flex;
align-items: center;
@@ -890,7 +900,6 @@ $border-color: #eeeeee;
.learn {
justify-content: space-between;
margin-top: 20rpx;
flex-wrap: wrap;
.item {
@@ -917,6 +926,7 @@ $border-color: #eeeeee;
.titleBox {
align-items: center;
margin-bottom: 20rpx;
image {
width: 50rpx;

139
pages/index.vue Normal file
View File

@@ -0,0 +1,139 @@
<template>
<div class="menu-container">
<!-- 一级导航 -->
<div class="menu-level-1">
<div
v-for="item in level1"
:key="item.id"
class="menu-item"
:class="{ active: selectedParentId === item.id }"
@click="selectParent(item)"
>
{{ item.name }}
</div>
</div>
<!-- 二级导航区域 -->
<transition name="fade">
<div
v-if="childList.length"
class="menu-level-2"
:style="{ gridRowStart: childRowIndex }"
>
<div
v-for="child in childList"
:key="child.id"
class="menu-item child"
>
{{ child.name }}
</div>
</div>
</transition>
</div>
</template>
<script setup>
import { ref, computed } from "vue"
// 模拟接口返回的一级导航
const level1 = ref([
{ id: 1, name: "导航1", children: [] },
{ id: 2, name: "导航2", children: [
{ id: 21, name: "子2-1" },
{ id: 22, name: "子2-2" },
{ id: 23, name: "子2-3" }
]},
{ id: 3, name: "导航3", children: [] },
{ id: 4, name: "导航4", children: [
{ id: 41, name: "子4-1" },
{ id: 42, name: "子4-2" }
]},
{ id: 5, name: "导航5", children: [] },
{ id: 6, name: "导航6", children: [
{ id: 61, name: "子6-1" },
{ id: 62, name: "子6-2" },
{ id: 63, name: "子6-3" },
{ id: 64, name: "子6-4" },
]},
{ id: 7, name: "导航7", children: [] },
{ id: 8, name: "导航8", children: [] }
])
// 选择的一级导航
const selectedParentId = ref(null)
// 当前一级导航的子级
const childList = computed(() => {
const parent = level1.value.find(i => i.id === selectedParentId.value)
return parent ? parent.children : []
})
// 计算二级导航应该显示在哪一行(每行 4 个)
const childRowIndex = computed(() => {
if (!selectedParentId.value) return 3
const index = level1.value.findIndex(i => i.id === selectedParentId.value)
return Math.floor(index / 4) + 2 // 第一行 row=1二级导航从 row=2 或 row=3
})
const selectParent = (item) => {
// 点击同一个时关闭
if (selectedParentId.value === item.id) {
selectedParentId.value = null
} else {
selectedParentId.value = item.id
}
}
</script>
<style scoped>
.menu-container {
display: grid;
grid-template-rows: auto auto auto;
gap: 10px;
}
/* 一级导航4列布局 */
.menu-level-1 {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.menu-item {
padding: 12px;
text-align: center;
background: #4a90e2;
color: white;
border-radius: 6px;
cursor: pointer;
}
.menu-item.active {
background: #2d73c7;
}
/* 二级导航:自动换行 */
.menu-level-2 {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
background: #2d73c7;
padding: 10px;
border-radius: 6px;
}
.menu-item.child {
background: #1e4f8a;
}
/* 动画 */
.fade-enter-active, .fade-leave-active {
transition: all .2s ease;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
transform: translateY(-5px);
}
</style>

View File

@@ -1,6 +1,8 @@
<template>
<view class="page">
<view class="title" :style="{ 'margin-top': getNotchHeight() + 'px' }">{{ $t('forget.title') }}</view>
<!-- 自定义导航栏 -->
<nav-bar :title="$t('forget.title')" />
<!-- <view class="title" :style="{ 'margin-top': getNotchHeight() + 'px' }">{{ $t('forget.title') }}</view> -->
<!-- 邮箱输入 -->
<view class="input-box">
@@ -33,7 +35,7 @@
<input
class="input-text"
type="password"
maxlength="20"
:maxlength="20"
v-model="password"
:placeholder="$t('forget.passwordPlaceholder')"
@input="inputMethod(password)"
@@ -54,8 +56,8 @@
<input
class="input-text"
type="password"
minlength="8"
maxlength="20"
:minlength="8"
:maxlength="20"
v-model="confirmPassword"
:placeholder="$t('forget.passwordAgainPlaceholder')"
/>

View File

@@ -34,7 +34,7 @@
v-model="code"
:placeholder="$t('login.codePlaceholder')"
placeholder-class="grey"
maxlength="6"
:maxlength="6"
@confirm="onSubmit"
/>
<wd-button type="info" :class="['code-btn', { 'active': !readonly }]" @click="onSetCode">
@@ -115,8 +115,8 @@
</view>
<!-- 游客体验 -->
<view class="youke-l">
<view @click="onPageJump('/pages/course/index')">
<view class="youke-l" v-if="!isAndorid">
<view @click="onPageSwitch('/pages/course/index')">
{{ $t('login.noLogin') }}
</view>
</view>
@@ -147,6 +147,7 @@ import { loginWithCode, loginWithPassword } from '@/api/modules/auth'
import { commonApi } from '@/api/modules/common'
import { validateEmail } from '@/utils/validator'
import { onPageJump } from '@/utils'
import { ILoginResponse } from '@/types/user'
import { t } from '@/utils/i18n'
@@ -184,6 +185,7 @@ let codeTimer: any = null
// 提交点击次数
const submitClickNum = ref(0)
const isAndorid = ref(false)
/**
* 切换登录方式
@@ -302,14 +304,14 @@ const passwordLogin = async () => {
const onSubmit = async () => {
if(!isAgree()) return false
let res = null
let res: ILoginResponse | null = null
switch (loginType.value) {
case 2000:
res = await verifyCodeLogin()
res = await verifyCodeLogin() as ILoginResponse
break
case 1000:
res = await passwordLogin()
res = await passwordLogin() as ILoginResponse
break
}
@@ -398,7 +400,8 @@ const yszc = () => {
/**
* 页面跳转
*/
const onPageJump = (url: string) => {
const onPageSwitch = (url: string) => {
uni.setStorageSync('state', 'true');
uni.switchTab({
url: url,
})
@@ -437,7 +440,17 @@ const agreeAgreements = () => {
uni.setStorageSync('Agreements_agreed', agree.value);
}
/**
* 判断当前系统
*/
const getOS = () =>{
const oprateOs = uni.getSystemInfoSync().platform
console.log(oprateOs, 'oprateOs');
isAndorid.value = oprateOs === "android" ? true : false
}
onMounted(() => {
getOS()
loadAgreements()
})
</script>

View File

@@ -7,7 +7,7 @@
<!-- 应用信息 -->
<view class="app-info">
<image
src="/static/icon/home_icon_logo.jpg"
src="/static/logo.png"
mode="aspectFit"
class="app-logo"
/>
@@ -52,11 +52,9 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { t } from '@/utils/i18n'
import { makePhoneCall } from '@/utils/index'
const { t } = useI18n()
// 导航栏高度
const statusBarHeight = ref(0)
const navbarHeight = ref('44px')

View File

@@ -1,5 +1,5 @@
<template>
<view class="user-page" :style="{ paddingTop: getNotchHeight() + 30 + 'px' }" v-if="userStore.token">
<view class="user-page" :style="{ paddingTop: getNotchHeight() + 30 + 'px' }" v-if="tokenState">
<!-- 设置图标 -->
<view class="settings-icon" :style="{ top: getNotchHeight() + 30 + 'px' }" @click="goSettings">
<wd-icon name="setting1" size="24px" color="#666" />
@@ -97,7 +97,6 @@
// 默认头像
const defaultAvatar = '/static/logo.png'
// 用户信息
const userInfo = computed(() => userStore.userInfo)
@@ -125,6 +124,13 @@
url: '/pages/user/myBook/index',
type: 'pageJump'
},
{
id: 8,
name: t('user.iHufen'),
url: '/pages/user/hufen/index',
type: 'pageJump',
hufenState: true
},
{
id: 3,
name: t('user.profile'),
@@ -143,29 +149,23 @@
url: '/pages/user/feedback/index',
type: 'pageJump'
},
// {
// id: 6,
// name: t('user.dataMigrate'),
// url: '/pages/user/migrate/index',
// desc: t('user.migrateSubtitle'),
// type: 'pageJump'
// }
{
id: 6,
name: t('user.dataMigrate'),
url: '/pages/user/migrate/index',
desc: t('user.migrateSubtitle'),
type: 'pageJump'
},
// {
// id: 7,
// name: t('user.certificate'),
// url: '/pages/user/certificate/index',
// type: 'pageJump'
// },
{
id: 8,
name: t('user.iHufen'),
url: '/pages/user/hufen/index',
type: 'pageJump',
hufenState: true
},
])
// 湖分
const hufenData = ref()
const tokenState = ref(false)
/**
* 获取平台信息
@@ -191,7 +191,6 @@
*/
const getHufen = async () => {
hufenData.value = await getUserContributionData()
hufenData.value = await getUserContributionData()
}
/**
@@ -274,22 +273,21 @@
url: '/pages/user/points/index'
})
}
onShow(() => {
console.log(userInfo, 'userInfo');
if (userStore.token) {
if (uni.getStorageSync('token')) {
tokenState.value = true
getData()
getHufen()
}
})
onMounted(() => {
console.log(userInfo, 'userInfo');
if (userStore.token) {
if (uni.getStorageSync('token')) {
getPlatform()
getHufen()
}
}
})
</script>

View File

@@ -96,7 +96,7 @@ const formData = ref({
const getMigrateInfo = async () => {
const res = await getUserMigrateInfo()
migrateInfo.value.alreadyMigration = res.alreadyMigration
// migrateInfo.value.notMigration = res.notMigration
migrateInfo.value.notMigration = res.notMigration
}
onMounted(() => {
getMigrateInfo()
@@ -151,6 +151,9 @@ const submitMigrate = async () => {
// 清空表单
formData.value.tel = ''
formData.value.code = ''
// 刷新用户迁移信息
getMigrateInfo()
}
</script>

View File

@@ -12,11 +12,11 @@
</view>
<view class="title">{{$t('order.pointsRecord')}}</view>
<view class="recharge-record-block" v-for="(item, index) in bookList" :key="index" @click="toDetails(item)">
<view class="recharge-record-block-row">{{item.remark.slice(0, (item.remark.indexOf(',')))}}<text
:class="item.actType === 1 ? 'text1' : 'text2'">{{item.actType === 1 ? '' : '+'}}{{item.changeAmount}}</text>
<view class="recharge-record-block-row">{{item.relationId ? item.remark.slice(0, (item.remark.indexOf(','))) : $t('user.backEnd')}}<text
:class="item.changeAmount < 0 ? 'text1' : 'text2'">{{item.changeAmount < 0 ? '' : '+'}}{{item.changeAmount}}</text>
</view>
<view class="time">{{item.createTime}}</view>
<view style="font-size: 24rpx;">{{item.remark.slice((item.remark.indexOf(','))+1)}}<wd-icon name="file-copy"
<view style="font-size: 24rpx;" v-if="item.relationId">{{item.remark.slice((item.remark.indexOf(','))+1)}}<wd-icon name="file-copy"
size="14px" color="#65A1FA" style="margin-left: 10rpx;" @click="copyToClipboard()"></wd-icon></view>
</view>
</view>
@@ -69,10 +69,14 @@
* 跳转订单详情
*/
const toDetails = (order: IOrder) => {
console.log(order.relationId, "order");
uni.navigateTo({
url: '/pages/user/order/details?orderId=' + order.relationId
})
if (order.relationId) {
uni.navigateTo({
url: '/pages/user/order/details?orderId=' + order.relationId
})
} else {
uni.showToast({ title: t('user.cannotView'), icon: 'none' });
}
}
</script>

View File

@@ -3,13 +3,13 @@
<!-- 自定义导航栏 -->
<nav-bar :title="$t('order.recharge')"></nav-bar>
<!-- 活动充值金额 -->
<view class="block" v-if="eventAmountList.length > 0">
<view class="block" v-if="eventAmountList?.length > 0">
<!-- <view class="text">{{$t('order.rechargeAmount')}}</view> -->
<view class="text">活动充值金额</view>
<view class="recharge">
<view class="recharge_block" @click="chosPric(item)"
:class="aloneItem.priceTypeId === item.priceTypeId ? 'selected' : ''"
v-for="item in eventAmountList" :key="item.priceTypeId">
:class="aloneItem.priceTypeId === item.priceTypeId ? 'selected' : ''" v-for="item in eventAmountList"
:key="item.priceTypeId">
<view class="recharge_money">NZ${{item.realMoney}}</view>
<view style="font-size: 26rpx;">{{item.money}}{{ $t('global.coin') }}</view>
<span class="activity-label" v-if="item.givejf >0">{{item.description}}</span>
@@ -23,8 +23,8 @@
<view class="text">{{$t('order.rechargeAmount')}}</view>
<view class="recharge">
<view class="recharge_block" @click="chosPric(item)"
:class="aloneItem.priceTypeId === item.priceTypeId ? 'selected' : ''"
v-for="item in standardAmountList" :key="item.priceTypeId">
:class="aloneItem.priceTypeId === item.priceTypeId ? 'selected' : ''" v-for="item in standardAmountList"
:key="item.priceTypeId">
<view class="recharge_money">NZ${{item.realMoney}}</view>
<view style="font-size: 26rpx;">{{item.money}}{{ $t('global.coin') }}</view>
<span class="activity-label" v-if="item.givejf >0">{{item.description}}</span>
@@ -39,13 +39,11 @@
<view class="cha_fangsh">
<view class="cf_title">{{$t('user.paymentMethod')}}</view>
<view class="cf_radio">
<radio-group v-for="item in iosPaylist">
<radio-group>
<view>
<view :class="payType == item.id ? 'Tab_xf cf_xuanx' : 'cf_xuanx'">
<!-- <image class="pay_item_img" :src="item.imgUrl" mode="aspectFil">
</image> -->
<text>{{ item.title }}</text>
<radio :checked="payType === item.id" @click="choseType(item.id)"></radio>
<view class="cf_xuanx">
<text>{{ isAndroid ? $t('user.googlePay') : $t('user.applePay')}}</text>
<radio checked="true;" @click="choseType(item.id)"></radio>
</view>
</view>
</radio-group>
@@ -80,7 +78,7 @@
import { ref, computed, onMounted, toRefs, reactive } from 'vue'
import { useI18n } from 'vue-i18n'
import { useMessage } from '@/uni_modules/wot-design-uni'
import { getBookBuyConfigList, getAgreement, getActivityDescription, verifyGooglePay, getPlaceOrder } from '@/api/modules/user'
import { getBookBuyConfigList, getAgreement, getActivityDescription, verifyGooglePay, getPlaceOrder, getIosPayment } from '@/api/modules/user'
import { useUserStore } from '@/stores/user'
import { useThrottle } from '@/hooks/useThrottle';
@@ -88,14 +86,6 @@
const userStore = useUserStore()
const { t } = useI18n()
const message = useMessage()
const payType = ref('1')
const iosPaylist = ref([
{
title: "Google Pay",
id: '1',
// imgUrl: "/static/icon/currency.png"
}
])
// 充值列表
const rechargeList = ref([])
// 金额列表单独每项
@@ -135,6 +125,8 @@
const eventAmountList = ref([])
//正常金额数据
const standardAmountList = ref([])
// 声明ios实例
const iapChannel = ref(null)
/**
* 获取使用环境
@@ -145,7 +137,8 @@
isAndroid.value = true;
console.log('运行Android上')
} else {
qudao.value = 'Google'
isAndroid.value = false;
qudao.value = 'IOS'
console.log('运行iOS上')
}
getData()
@@ -189,10 +182,11 @@
* 获取订单编号
*/
const getPlaceOrderObj = async () => {
console.log(isAndroid.value);
const { priceTypeId, realMoney, money } = toRefs(aloneItem.value)
const data = {
userId: userStore.userInfo.id, // 用户di
paymentMethod: '5', //支付方式4point 5google
paymentMethod: isAndroid.value ? '5' : '3', //支付方式3ios 5google
orderMoney: money.value, //订单金额
realMoney: realMoney.value, //实际金额
come: '10', //订单来源 2医学吴门医述 10海外读书
@@ -200,12 +194,12 @@
productId: priceTypeId.value // 商品id
}
try {
// uni.hideLoading()
const res = await getPlaceOrder(data)
orderSn.value = res.orderSn
console.log(orderSn.value, '获取订单号');
uni.showLoading({ title: t('order.orderCreating') })
getGooglePay()
isAndroid.value ? getGooglePay() : checkProvider()
// getGooglePay()
} catch (error) {
console.error('获取订单号失败', error)
}
@@ -233,7 +227,121 @@
}
/**
* 初始化
* 检测支付提供商判断是否支持applepay
*/
const checkProvider = () => {
uni.getProvider({
service: 'payment',
success: (res) => {
console.log('getProvider返回结果', res); // 关键日志看是否包含applepay
iapChannel.value = res.providers.find((channel) => {
return (channel.id === 'appleiap')
})
getProductInfo()
}
});
}
/**
* 查询后台配置的商品信息
*/
const getProductInfo = () => {
const id = String(aloneItem.value.priceTypeId)
iapChannel.value.requestProduct([id], (res : any) => {
console.log(res, '查询苹果后台配置的商品id');
topay(id)
}, (err : any) => {
uni.showToast({ title: '未获取到产品信息,请联系管理员', icon: 'none' });
console.error('失败', err);
});
}
/**
* 准备支付-调出支付窗口
*/
const topay = (id : string) => {
return new Promise((resolve, reject) => {
uni.hideLoading()
uni.requestPayment({
provider: 'appleiap',
orderInfo: {
productid: id,
username: orderSn.value, // 订单id
quantity: 1,
manualFinishTransaction: true
},
success: (res) => {
console.log(res, 'res-topay');
iapCheck(res);
resolve(res);
},
fail: (err) => {
console.log('支付错误', err);
restoreComplateRequest()
uni.showToast({ title: t('user.closeWindow'), icon: 'error' })
reject(err);
}
});
})
}
/**
* 调用后台支付-ios
*/
const iapCheck = async (res : { transactionIdentifier : string; transactionReceipt : string }) => {
// console.log(res.transactionIdentifier,res.payment.productid,res.payment.username,res.transactionReceipt,userStore.userInfo.id);
try {
const obj = await getIosPayment(res.transactionIdentifier, res.payment.productid, res.payment.username, res.transactionReceipt, userStore.userInfo.id)
console.log(obj, '校验订单')
finishTransaction(res)
uni.switchTab({
url: '/pages/user/index'
})
} catch (error) {
console.error('校验订单失败:', error)
// 也需要释放订单,防止失败再次提交支付窗口拉不起来
finishTransaction(res)
}
}
/**
* 检查是否存在未关闭的订单
*/
const restoreComplateRequest = () => {
return new Promise((resolve, reject) => {
iapChannel.value.restoreCompletedTransactions({
manualFinishTransaction: true,
}, (res : unknown) => {
console.log(res, '成功-restoreCompletedTransactions');
res.map((item : any) => {
finishTransaction(item)
})
resolve(res);
}, (err : any) => {
console.log(err, '失败-restoreCompletedTransactions');
reject(err);
})
});
}
/**
* 关闭订单
*/
const finishTransaction = (trans : any) => {
iapChannel.value.finishTransaction(
trans,
(success : any) => {
console.log("关闭订单成功", success);
},
(fail : any) => {
console.log("关闭订单失败", fail);
}
);
}
/**
* 谷歌初始化
*/
const getGooglePay = () => {
googlePay.init({
@@ -375,13 +483,6 @@
}
}
/**
* 切换支付方式
*/
const choseType = () => {
// payType.value = val;
}
onMounted(() => {
getDevName();
getActivityDescriptionData()
@@ -403,7 +504,7 @@
color: #007bff;
padding: 30rpx 0 20rpx 30rpx;
}
.recharge {
display: flex;
flex-wrap: wrap;

View File

@@ -16,10 +16,10 @@
<view class="title">{{$t('order.rechargeConsumptionList')}}</view>
<view class="recharge-record-block" v-for="(item, index) in bookList" :key="index" @click="toDetails(item)">
<view class="recharge-record-block-row">{{item.orderType}}<text
:class="item.orderType !== '充值' ? 'text1' : 'text2'">{{item.orderType !== '充值' ? '' : '+'}}{{item.changeAmount}}</text>
:class="item.changeAmount < 0 ? 'text1' : 'text2'">{{item.changeAmount < 0 ? '' : '+'}}{{item.changeAmount}}</text>
</view>
<view class="recharge-record-block-row_">{{item.productName}}</view>
<view class="recharge-record-block-row_">{{$t('user.orderSn')}}{{item.payNo}}<wd-icon name="file-copy"
<view class="recharge-record-block-row_" v-if="item.relationId">{{$t('user.orderSn')}}{{item.payNo}}<wd-icon name="file-copy"
size="14px" color="#65A1FA" style="margin-left: 10rpx;" @click="copyToClipboard()"></wd-icon>
</view>
<view class="time">{{item.createTime}}</view>
@@ -75,10 +75,14 @@
* 跳转订单详情
*/
const toDetails = (order: IOrder) => {
console.log(order.relationId, "order");
uni.navigateTo({
url: '/pages/user/order/details?orderId=' + order.relationId
})
if (order.relationId) {
uni.navigateTo({
url: '/pages/user/order/details?orderId=' + order.relationId
})
} else {
uni.showToast({ title: t('user.cannotView'), icon: 'none' });
}
}
</script>

View File

@@ -4,7 +4,7 @@
<nav-bar :title="$t('vip.bookVip')" />
<!-- VIP介绍卡片 -->
<view class="vip-intro-card">
<view class="vip-intro-card mt-2!">
<view class="vip-intro-header">
<text class="vip-intro-title">📚 读书VIP特权</text>
<!-- <text class="vip-intro-subtitle">畅享海量电子图书</text> -->
@@ -58,14 +58,15 @@
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { useUserStore } from '@/stores/user'
import { vipApi } from '@/api/modules/vip'
import type { IVipBook } from '@/types/vip'
const userStore = useUserStore()
const vipList = ref([])
const vipList = ref<IVipBook[]>([])
const getVipList = async () => {
const res = await vipApi.getBookVipList()
// 模拟推荐标识,实际项目中应该从后端获取
@@ -100,7 +101,10 @@ onShow(() => {
<style lang="scss" scoped>
.page-wrapper {
padding: 20rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
// background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-color: #6E63C4;
background-size: cover;
background-position: center;
min-height: 100vh;
}

View File

@@ -19,9 +19,9 @@
import { ref } from 'vue'
const menuItems = ref([
{
name: '分享APP'
},
// {
// name: '分享APP'
// },
{
name: '关于我们',
}
@@ -55,10 +55,10 @@
provider: "weixin",
scene: "WXSceneSession",
type: 0,
href: '',
href: 'https://a.app.qq.com/o/simple.jsp?pkgname=com.cn.medicine',
title: "吴门医述",
summary: "我正在使用吴门医述提升自己,赶紧跟我一起来体验吧!",
imageUrl: "static/icon/home_icon_logo.png",
imageUrl: "static/logo.png",
success: function (res) {
console.log("success:" + JSON.stringify(res));
},
@@ -72,10 +72,10 @@
provider: "weixin",
scene: "WXSceneTimeline",
type: 0,
href: '',
href: 'https://a.app.qq.com/o/simple.jsp?pkgname=com.cn.medicine',
title: "吴门医述",
summary: "我正在使用吴门医述提升自己,赶紧跟我一起来体验吧!",
imageUrl: "static/icon/home_icon_logo.png",
imageUrl: "static/logo.png",
success: function (res) {
console.log("success:" + JSON.stringify(res));
},
@@ -110,7 +110,7 @@
}
.visitor-block {
padding: 40rpx 20rpx;
padding: 100rpx 20rpx 40rpx 20rpx;
.visitor_img {
width: 150rpx;

View File

Before

Width:  |  Height:  |  Size: 261 KiB

After

Width:  |  Height:  |  Size: 261 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -8,8 +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-gray-300: oklch(87.2% 0.01 258.338);
--color-gray-500: oklch(55.1% 0.027 264.364);
--color-white: #fff;
--spacing: 0.25rem;
--text-xs: 0.75rem;
@@ -220,15 +218,6 @@
.mt-2\! {
margin-top: calc(var(--spacing) * 2) !important;
}
.mt-2\.5\! {
margin-top: calc(var(--spacing) * 2.5) !important;
}
.mt-3 {
margin-top: calc(var(--spacing) * 3);
}
.mt-3\! {
margin-top: calc(var(--spacing) * 3) !important;
}
.mt-5 {
margin-top: calc(var(--spacing) * 5);
}
@@ -244,21 +233,6 @@
.mt-\[20rpx\]\! {
margin-top: 20rpx !important;
}
.mr-\[20rpx\] {
margin-right: 20rpx;
}
.mr-\[20rpx\]\! {
margin-right: 20rpx !important;
}
.mb-1 {
margin-bottom: calc(var(--spacing) * 1);
}
.mb-1\! {
margin-bottom: calc(var(--spacing) * 1) !important;
}
.mb-1\.5\! {
margin-bottom: calc(var(--spacing) * 1.5) !important;
}
.mb-2 {
margin-bottom: calc(var(--spacing) * 2);
}
@@ -283,12 +257,6 @@
.ml-2\.5\! {
margin-left: calc(var(--spacing) * 2.5) !important;
}
.ml-\[20rpx\] {
margin-left: 20rpx;
}
.ml-\[20rpx\]\! {
margin-left: 20rpx !important;
}
.block {
display: block;
}
@@ -344,12 +312,6 @@
border-style: var(--tw-border-style);
border-width: 1px;
}
.bg-gray-300 {
background-color: var(--color-gray-300);
}
.bg-gray-500 {
background-color: var(--color-gray-500);
}
.bg-white {
background-color: var(--color-white);
}
@@ -359,12 +321,6 @@
.p-0\! {
padding: calc(var(--spacing) * 0) !important;
}
.p-2 {
padding: calc(var(--spacing) * 2);
}
.p-2\.5 {
padding: calc(var(--spacing) * 2.5);
}
.p-3 {
padding: calc(var(--spacing) * 3);
}
@@ -374,9 +330,6 @@
.p-\[20rpx\] {
padding: 20rpx;
}
.p-\[20rpx\]\! {
padding: 20rpx !important;
}
.p-\[30rpx\] {
padding: 30rpx;
}

5
types/course.d.ts vendored
View File

@@ -50,11 +50,6 @@ export interface IMarketCourseListResponse extends IApiResponse {
}
}
/** 消息列表响应 */
export interface IMessageListResponse extends IApiResponse {
messages: INews[] // 消息列表
}
/** 课程详情 */
export interface ICourseDetail {
id: number

View File

@@ -1,4 +1,5 @@
// types/user.ts
import type { IApiResponse } from '@/api/types'
/**
*
@@ -16,12 +17,12 @@ export interface IUserInfo {
/**
*
*/
export interface ILoginResponse {
userInfo: IUserInfo
export interface ILoginResponse<T = IUserInfo> extends IApiResponse {
token: {
token: string
[key: string]: any
}
},
userInfo: T
}
/**

12
types/vip.d.ts vendored
View File

@@ -24,4 +24,16 @@ export interface IVipItemProduct {
fee: number | null, // 课程价格
lastFee?: number | null, // 未使用字段
[key: string]: any
}
/**
* 电子书VIP套餐
*/
export interface IVipBook {
id: number
type: number
title: string
money: number // vip金额
days: number // vip天数
[key: string]: any
}

View File

@@ -232,6 +232,18 @@ export function useUpload(): UseUploadReturn {
extension
}: ChooseFileOption): Promise<ChooseFile[]> {
return new Promise((resolve, reject) => {
if(uni.getSystemInfoSync().platform === 'ios') {
uni.chooseImage({
count: multiple ? maxCount : 1,
mediaType: ['image'],
sizeType,
sourceType,
extension,
success: (res) => resolve(formatImage(res)),
fail: reject
})
return
}
switch (accept) {
case 'image':
// #ifdef MP-WEIXIN