This commit is contained in:
2025-11-28 11:50:07 +08:00
21 changed files with 168 additions and 160 deletions

View File

@@ -7,8 +7,8 @@ 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: 'http://192.168.110.100:9300/pb/', // 张川川
MAIN: 'https://global.nuttyreading.com/', // 线上
// PAYMENT: 'https://dev-pay.example.com', // 暂时用不到
// CDN: 'https://cdn-dev.example.com', // 暂时用不到
},

View File

@@ -0,0 +1,49 @@
<template>
<view class="book-price-container">
<view v-if="data.isBuy" class="book-flag">已购买</view>
<view v-else-if="data.isVip == '0'" class="book-flag">免费</view>
<view v-else-if="userHasVip && data.isVip == '1'" class="book-price">VIP免费</view>
<view v-else class="book-price">{{ item.minPrice }} {{ $t('global.coin') }}</view>
<view>
<text v-if="data.readCount" class="book-flag">{{ `${data.readCount}${$t('bookHome.readingCount')}` }}</text>
<text v-else-if="data.buyCount" class="book-flag">{{ `${data.buyCount}${$t('bookHome.purchased')}` }}</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import type { IBook } from '@/types/book'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 检查用户是否为VIP
const userHasVip = computed(() => userStore.userInfo?.userEbookVip?.length > 0)
const props = defineProps({
data: {
type: Object as () => IBook,
default: () => ({})
}
})
</script>
<style lang="scss" scoped>
.book-price-container {
display: flex;
align-items: center;
justify-content: space-between;
}
.book-price {
font-size: 28rpx;
color: #ff4703;
}
.book-flag {
font-size: 26rpx;
color: #999;
}
</style>

View File

@@ -14,13 +14,13 @@
<text v-else>
课程有效期截止到{{ catalogue.endTime }}
</text>
<wd-button
<!-- <wd-button
v-if="catalogue.startTime"
size="small"
@click="handleRenew"
>
续费
</wd-button>
</wd-button> -->
</template>
</view>
</view>

View File

@@ -62,7 +62,7 @@
<!-- 积分输入 -->
<view v-if="allowPointPay && userInfo?.jf > 0" class="points-input-section">
<text class="points-label">
{{ $t('order.maxPoints', { max: pointsUsableMax }) }}
{{ $t('order.maxPoints').replace('max', pointsUsableMax) }}
</text>
<view class="points-input-box">
<input
@@ -136,16 +136,16 @@ const userStore = useUserStore()
interface Props {
goodsList: IGoods[],
userInfo: object,
allowPointPay: boolean,
orderType: string,
backStep: number // 购买完成后返回几层页面
allowPointPay?: boolean,
orderType?: string,
backStep?: number // 购买完成后返回几层页面
}
const props = withDefaults(defineProps<Props>(), {
goodsList: () => [],
userInfo: () => ({}),
allowPointPay: () => false,
orderType: () => '',
backStep: () => 1
allowPointPay: true,
orderType: 'order',
backStep: 1
})
// 订单备注
@@ -373,9 +373,11 @@ const handleSubmit = async () => {
})
// 返回上一页
uni.navigateBack({
delta: props.backStep
})
setTimeout(() => {
uni.navigateBack({
delta: props.backStep
})
}, 500)
}
/**

View File

@@ -52,7 +52,7 @@ const props = defineProps({
*/
const goToRecharge = () => {
uni.navigateTo({
url: '/pages/user/wallet/recharge/index?source=order'
url: '/pages/user/recharge/index'
})
}
</script>

View File

@@ -33,6 +33,8 @@ const productImg = computed(() => {
return props.data?.images || ''
case 'vip':
return '/static/vip.png'
case 'abroadVip':
return '/static/vip.png'
case 'point':
return '/static/jifen.png'
default:
@@ -47,6 +49,8 @@ const title = computed(() => {
return props.data?.name || ''
case 'vip':
return props.data?.title + '<text style="color: #ff4703; font-weight: bold;">(' + props.data?.year + '年)</text>' || ''
case 'abroadVip':
return '电子书VIP' + props.data?.title + '<text style="color: #ff4703; font-weight: bold;">(' + props.data?.days + '天)</text>' || ''
case 'point':
return ''
default:
@@ -61,6 +65,8 @@ const price = computed(() => {
return props.data?.abroadPrice || 0
case 'vip':
return props.data?.fee || 0
case 'abroadVip':
return props.data?.money || 0
case 'point':
return ''
default:

View File

@@ -298,7 +298,9 @@
"listen": {
"title": "Audio Book",
"speed": "Playback Speed",
"chapterList": "Chapter List"
"chapterList": "Chapter List",
"isLast": "Last Chapter",
"isFirst": "First Chapter"
},
"workOrder": {
"submit_success": "Submitted successfully"
@@ -443,7 +445,7 @@
"notUseCoupon": "Don't use coupon",
"reselect": "Reselect",
"selected": "Confirm",
"maxPoints": "Available Points ({max} pts)",
"maxPoints": "Available Points (max pts)",
"pointsPlaceholder": "Enter points",
"allPoints": "Total Points",
"insufficientBalance": "Insufficient virtual coin balance",

View File

@@ -298,7 +298,9 @@
"listen": {
"title": "听书",
"speed": "播放速度",
"chapterList": "章节列表"
"chapterList": "章节列表",
"isLast": "已到最后一章",
"isFirst": "已到第一章"
},
"workOrder": {
"submit_success": "提交成功"
@@ -443,7 +445,7 @@
"notUseCoupon": "不使用优惠券",
"reselect": "重新选择",
"selected": "选好了",
"maxPoints": "可用积分({max}分)",
"maxPoints": "可用积分(max分)",
"pointsPlaceholder": "请输入积分",
"allPoints": "全部积分",
"insufficientBalance": "天医币余额不足",

View File

@@ -1,5 +1,5 @@
{
"pages": [ //pages数组中第一项表示应用启动页参考https://uniapp.dcloud.io/collocation/pages
"pages": [
{
"path": "pages/course/index",
"style": {

View File

@@ -114,31 +114,6 @@
@confirm="handlePurchase"
@close="closePurchasePopup"
/>
<!-- <wd-popup v-model="purchaseVisible" position="bottom">
<view class="purchase-popup">
<view class="book-info-mini">
<image :src="bookInfo.images" mode="aspectFill" />
<view class="info">
<text class="name">{{ bookInfo.name }}</text>
<text v-if="bookInfo.priceData" class="price">
$ {{ bookInfo.priceData.dictValue }} NZD
</text>
</view>
</view>
<view class="spec-section">
<text class="spec-title">{{ $t('bookDetails.list') }}</text>
<view class="spec-item active">
<text>{{ bookInfo.name }}</text>
<text v-if="bookInfo.priceData" class="spec-price">
${{ bookInfo.priceData.dictValue }} NZD
</text>
</view>
</view>
<wd-button type="primary" block @click="handlePurchase">
{{ $t('bookDetails.buy') }}
</wd-button>
</view>
</wd-popup> -->
</view>
</template>

View File

@@ -162,10 +162,7 @@
>
<image :src="item.images" />
<text class="book-text">{{ item.name }}</text>
<text class="book-price">{{ item.minPrice }} {{ t('global.coin') }}</text>
<text v-if="formatStats(item)" class="book-flag">{{
formatStats(item)
}}</text>
<BookPrice :data="item" class="book-price-container" />
</view>
</view>
<text v-else class="zanwu" style="padding: 100rpx 0">{{ $t('global.dataNull') }}</text>
@@ -177,9 +174,9 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import { homeApi } from '@/api/modules/book_home'
import { getNotchHeight } from '@/utils/system'
import BookPrice from '@/components/book/BookPrice.vue'
import type {
IBook,
IBookWithStats,
@@ -187,8 +184,6 @@ import type {
IVipInfo
} from '@/types/book'
const { t } = useI18n()
// 状态定义
const showMyBooks = ref(false)
const showActivity = ref(false)
@@ -320,43 +315,6 @@ const getBooksByLabel = async (
}
}
/**
* 格式化价格
*/
const formatPrice = (book: IBookWithStats): string => {
// 已购买不显示价格
if (book.isBuy) return ''
// VIP用户且图书为VIP专享
if (vipInfo.value?.id && book.isVip === '2') {
const price = book.sysDictData?.dictValue
return price ? `$ ${price} NZD` : ''
}
// 普通用户
if (!vipInfo.value?.id) {
const price = book.sysDictData?.dictValue
return price ? `$ ${price} NZD` : ''
}
return ''
}
/**
* 格式化统计信息
*/
const formatStats = (book: IBookWithStats): string => {
if (book.readCount && book.readCount > 0) {
return `${book.readCount}${t('bookHome.readingCount')}`
}
if (book.buyCount && book.buyCount > 0) {
return `${book.buyCount}${t('bookHome.purchased')}`
}
return ''
}
/**
* 处理搜索点击
*/
@@ -388,8 +346,8 @@ const handleBookClick = (bookId: number) => {
* 处理更多按钮点击
*/
const handleMoreClick = () => {
uni.switchTab({
url: '/pages/book/index'
uni.navigateTo({
url: '/pages/user/myBook/index'
})
}
@@ -779,21 +737,9 @@ onShow(() => {
overflow: hidden;
}
.book-price {
position: absolute;
font-size: 28rpx;
color: #ff4703;
left: 30rpx;
bottom: 20rpx;
}
.book-flag {
display: block;
font-size: 26rpx;
color: #999;
position: absolute;
right: 6%;
bottom: 20rpx;
.book-price-container {
width: 80%;
margin: 15rpx auto 0;
}
}
}

View File

@@ -381,7 +381,7 @@ async function prevChapter() {
playChapter(chapterList.value[currentChapterIndex.value])
} else {
uni.showToast({
title: t('listen.earlier'),
title: t('listen.isFirst'),
icon: 'none'
})
}
@@ -412,7 +412,7 @@ async function nextChapter() {
playChapter(chapterList.value[currentChapterIndex.value])
} else {
uni.showToast({
title: t('listen.behind'),
title: t('listen.isLast'),
icon: 'none'
})
}

View File

@@ -606,7 +606,7 @@ function changeReadMode(mode: 'scroll' | 'page') {
const bookLanguages = ref([])
const currentLanguage = ref('')
onMounted(() => {
currentLanguage.value = uni.getStorageSync('currentBookLanguage') || ''
currentLanguage.value = uni.getStorageSync('currentBookLanguage') || '中文'
console.log('currentLanguage', currentLanguage.value)
})
const getBookLanguages = async () => {

View File

@@ -130,6 +130,15 @@
<text>
本课程一经购买暂不支持退款敬请谅解
</text>
<view style="color: red; font-weight: bold"> : </view>
<view>
1.手机pad电脑均为可登陆电子设备均有唯一标识码一个用户名仅允许在一个手机或一个ipad或一个电脑登陆请根据您的使用习惯自行选择<br />
2.如若申请变更登陆设备请联系客服<br />
客服电话:13110039505;022-24142321<br />
客服微信号:yilujiankangkefu<br />
3.如因违反上述使用规定...概不退款本公司保留追究用户相关法律责任的权利<br />
4.点击同意按钮即表示您同意遵守以上条款
</view>
</view>
<view class="protocol-actions">
<wd-button type="info" plain @click="showProtocol = false">不同意</wd-button>
@@ -156,7 +165,7 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { onLoad, onPageScroll, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
import { onLoad, onPageScroll, onPullDownRefresh, onReachBottom, onShow } from '@dcloudio/uni-app'
import { useCourseStore } from '@/stores/course'
import { useUserStore } from '@/stores/user'
import { courseApi } from '@/api/modules/course'
@@ -252,6 +261,12 @@ const vipTip = computed(() => {
*/
onLoad(async (options: any) => {
courseId.value = parseInt(options.id)
})
/**
* 页面显示
*/
onShow(async () => {
await loadPageData()
})
@@ -809,7 +824,7 @@ onReachBottom(() => {
}
.protocol-content {
max-height: 500rpx;
max-height: 60vh;
overflow-y: auto;
font-size: 26rpx;
line-height: 1.8;

View File

@@ -48,7 +48,7 @@
<view
class="fourBox"
style="padding: 0; padding-bottom: 8rpx"
v-if="sbuMedicalTagsList && sbuMedicalTagsList.length > 0"
v-if="sbuMedicalTagsList?.length > 0"
>
<view
class="childrenBox fourIcon flexbox"
@@ -257,6 +257,7 @@ const handleFirstLevelClick = (item: string) => {
* 获取课程分类数据
*/
const getMedicalTags = async () => {
sbuMedicalTagsList.value = []
const res = await courseSubjectClassificationApi.getCourseMedicalTree()
if (res && res.code === 0) {
if (res.labels && res.labels.length > 0) {
@@ -268,8 +269,6 @@ const getMedicalTags = async () => {
// 非终极分类,显示子分类
if (selectedTag.children && selectedTag.children.length > 0) {
sbuMedicalTagsList.value = selectedTag.children
} else {
sbuMedicalTagsList.value = []
}
}
}

View File

@@ -1,25 +0,0 @@
<template>
<view class="container">
<view class="title bg-[transparent] text-center text-[#000]">这是一个等待开发的首页</view>
<view class="title bg-[blue] text-center text-[#fff]">这是一个等待开发的首页</view>
<view class="description bg-[red]">首页的内容是在线课程</view>
</view>
</template>
<script setup lang="ts">
</script>
<style>
.title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
}
.description {
font-size: 14px;
opacity: 0.6;
margin-bottom: 15px;
}
</style>

View File

@@ -4,7 +4,7 @@
<nav-bar :title="$t('order.confirmTitle')" />
<!-- 确认订单组件 -->
<Confirm :goodsList="goodsList" :userInfo="userInfo">
<Confirm :goodsList="goodsList" :userInfo="userInfo" :orderType="orderType">
<template #goodsList>
<!-- 商品列表内容 -->
<view
@@ -49,7 +49,7 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { orderApi } from '@/api/modules/order'
import type { IOrderGoods } from '@/types/order'
@@ -79,6 +79,14 @@ const getGoodsList = async () => {
}
}
// 复读
const isRelearn = ref<boolean>(false)
// 订单类型
const orderType = computed(() => {
return isRelearn.value ? 'relearn' : 'order'
})
/**
* 页面加载
*/
@@ -90,6 +98,7 @@ onLoad(async (options: any) => {
// 根据商品ID获取商品详细信息
goodsIds.value = options.goods || ''
isRelearn.value = options.isRelearn == '1'
getGoodsList()
} catch (error) {
console.error('解析商品数据失败:', error)

View File

@@ -26,22 +26,18 @@
<view class="vip-card-title">{{ $t('user.vip') }}</view>
<view class="vip-card-content">
<view class="vip-item-list">
<view v-if="vipInfo.length > 0" v-for="vip in vipInfo">{{ vipTypeDict[vip.type] }}有效期到
{{ parseTime(vip.endTime, '{y}-{m}-{d}') }}</view>
<view v-if="vipInfo.length > 0" v-for="vip in vipInfo">{{ vipTypeDict[vip.type] }}{{ parseTime(vip.endTime, '{y}-{m}-{d}') }} 截止</view>
<view v-else>办理课程VIP畅享更多权益</view>
</view>
<wd-button v-if="vipInfo.length > 0" plain type="primary" size="small"
@click="goSubscribe">{{ $t('vip.renewal') }}</wd-button>
<wd-button v-else plain type="primary" size="small" @click="goSubscribe">{{ $t('vip.openVip') }}</wd-button>
<wd-button v-if="vipInfo.length > 0" plain type="primary" size="small" @click="goCourseVipSub">{{ $t('vip.renewal') }}</wd-button>
<wd-button v-else plain type="primary" size="small" @click="goCourseVipSub">{{ $t('vip.openVip') }}</wd-button>
</view>
<view class="vip-card-content">
<view class="vip-item-list">
<view v-if="vipInfoEbook.length > 0" v-for="vip in vipInfoEbook">电子书VIP{{ vipTypeDict[vip.type] }}有效期到
{{ parseTime(vip.endTime, '{y}-{m}-{d}') }}</view>
<view v-if="vipInfoEbook.length > 0" v-for="vip in vipInfoEbook">电子书VIP{{ vipTypeDict[vip.type] }}{{ parseTime(vip.endTime, '{y}-{m}-{d}') }} 截止</view>
<view v-else>办理电子书VIP畅享更多权益</view>
</view>
<wd-button v-if="!vipInfoEbook.length" plain type="primary" size="small"
@click="goSubscribe">{{ $t('vip.openVip') }}</wd-button>
<wd-button v-if="!vipInfoEbook.length" plain type="primary" size="small" @click="goSubscribe">{{ $t('vip.openVip') }}</wd-button>
</view>
</view>
</view>
@@ -183,7 +179,7 @@
}
/**
* 跳转到订阅页面
* 跳转到电子书vip订阅页面
*/
const goSubscribe = () => {
uni.navigateTo({
@@ -191,6 +187,15 @@
})
}
/**
* 跳转到课程vip订阅页面
*/
const goCourseVipSub = () => {
uni.navigateTo({
url: '/pages/vip/course'
})
}
/**
* 处理菜单点击
*/

View File

@@ -26,6 +26,7 @@
<ProductInfo v-if="order.orderType === 'order'" :data="order.productList" :type="order.orderType" />
<ProductInfo v-if="order.orderType === 'abroadBook'" :data="order.bookEntity" :type="order.orderType" />
<ProductInfo v-if="order.orderType === 'vip'" :data="order.vipBuyConfigEntity" :type="order.orderType" />
<ProductInfo v-if="order.orderType === 'abroadVip'" :data="order.ebookvipBuyConfig" :type="order.orderType" />
<!-- 三种订单类型商品信息 end -->
<view class="order-item-total-price">实付款{{ order.orderMoney }} {{ t('global.coin') }}</view>

View File

@@ -73,15 +73,6 @@ const getVipList = async () => {
vipList.value = res.lableList || []
}
// 选择套餐
const selectPackage = (vip: any) => {
// 这里可以添加跳转到订单确认页面的逻辑
uni.showToast({
title: `已选择: ${vip.title}`,
icon: 'none'
})
}
// 处理购买
const handlePurchase = (vip: any) => {
const selectedGoods = {

View File

@@ -9,6 +9,7 @@
"Courier New", monospace;
--color-red-500: oklch(63.7% 0.237 25.331);
--spacing: 0.25rem;
--font-weight-bold: 700;
--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);
@@ -204,9 +205,18 @@
max-width: 96rem;
}
}
.mr-1 {
margin-right: calc(var(--spacing) * 1);
}
.ml-1 {
margin-left: calc(var(--spacing) * 1);
}
.ml-1\! {
margin-left: calc(var(--spacing) * 1) !important;
}
.ml-2 {
margin-left: calc(var(--spacing) * 2);
}
.ml-2\.5\! {
margin-left: calc(var(--spacing) * 2.5) !important;
}
@@ -243,6 +253,9 @@
.flex-shrink {
flex-shrink: 1;
}
.border-collapse {
border-collapse: collapse;
}
.transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
}
@@ -271,6 +284,12 @@
.pt-10 {
padding-top: calc(var(--spacing) * 10);
}
.pt-\[40px\] {
padding-top: 40px;
}
.pb-0 {
padding-bottom: calc(var(--spacing) * 0);
}
.pb-0\! {
padding-bottom: calc(var(--spacing) * 0) !important;
}
@@ -280,6 +299,10 @@
.text-right {
text-align: right;
}
.font-bold {
--tw-font-weight: var(--font-weight-bold);
font-weight: var(--font-weight-bold);
}
.text-\[\#000\] {
color: #000;
}
@@ -302,6 +325,9 @@
--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;
}
.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);
@@ -371,6 +397,10 @@
inherits: false;
initial-value: solid;
}
@property --tw-font-weight {
syntax: "*";
inherits: false;
}
@property --tw-ordinal {
syntax: "*";
inherits: false;
@@ -563,6 +593,7 @@
--tw-skew-x: initial;
--tw-skew-y: initial;
--tw-border-style: solid;
--tw-font-weight: initial;
--tw-ordinal: initial;
--tw-slashed-zero: initial;
--tw-numeric-figure: initial;