diff --git a/api/modules/course.ts b/api/modules/course.ts index c7b3cfd..eb411f1 100644 --- a/api/modules/course.ts +++ b/api/modules/course.ts @@ -114,7 +114,7 @@ export const courseApi = { }, /** - * 开始学习免费课程 + * 领取免费课程 * @param catalogueId 目录ID */ startStudyForMF(catalogueId: number) { diff --git a/api/modules/news.ts b/api/modules/news.ts new file mode 100644 index 0000000..12368fc --- /dev/null +++ b/api/modules/news.ts @@ -0,0 +1,27 @@ +import { mainClient } from '@/api/clients/main' +import type { IApiResponse } from '@/api/types' + +export const newsApi = { + /** + * 获取新闻详情 + */ + getNewsDetail: async (newsId: string | number) => { + const res = await mainClient.request>({ + url: `common/message/getMessageById?id=${newsId}`, + method: 'POST' + }) + return res + }, + + /** + * 获取太湖之光文章详情 + */ + getTaihuWelfareArticleDetail: async (newsId: string | number) => { + const res = await mainClient.request>({ + url: 'common/taihuWelfare/getTaihuWelfareArticleDetail', + method: 'POST', + data: { id: newsId } + }) + return res + } +} \ No newline at end of file diff --git a/components/book/BookPrice.vue b/components/book/BookPrice.vue index 9306db0..5c186c3 100644 --- a/components/book/BookPrice.vue +++ b/components/book/BookPrice.vue @@ -3,7 +3,7 @@ 已购买 免费 VIP免费 - {{ item.minPrice }} {{ $t('global.coin') }} + {{ data.minPrice }} {{ $t('global.coin') }} {{ `${data.readCount}${$t('bookHome.readingCount')}` }} {{ `${data.buyCount}${$t('bookHome.purchased')}` }} diff --git a/components/course/CatalogueList.vue b/components/course/CatalogueList.vue deleted file mode 100644 index da9ddef..0000000 --- a/components/course/CatalogueList.vue +++ /dev/null @@ -1,96 +0,0 @@ - - - - - diff --git a/components/course/ChapterList.vue b/components/course/ChapterList.vue deleted file mode 100644 index d6eeaf9..0000000 --- a/components/course/ChapterList.vue +++ /dev/null @@ -1,320 +0,0 @@ - - - - - diff --git a/components/order/Confirm.vue b/components/order/Confirm.vue index 344b448..285b648 100644 --- a/components/order/Confirm.vue +++ b/components/order/Confirm.vue @@ -290,7 +290,11 @@ const handlePointsInput = (value: any) => { } // 重新计算实付款 - calculateFinalPrice() + const result = Math.max( + 0, + totalAmount.value - pointsDiscounted.value - promotionDiscounted.value - vipDiscounted.value + ) + finalAmount.value = result } /** @@ -314,9 +318,11 @@ const calculateFinalPrice = () => { props?.userInfo?.jf || 0, Math.floor(orderAmountAfterDiscount - couponAmount) ) + + pointsDiscounted.value = pointsUsableMax.value // 限制当前积分不超过最大值 - if (pointsDiscounted.value > pointsUsableMax.value) { + if (pointsDiscounted.value >= pointsUsableMax.value) { pointsDiscounted.value = pointsUsableMax.value } diff --git a/locale/en.json b/locale/en.json index 45c650c..9a4a8e6 100644 --- a/locale/en.json +++ b/locale/en.json @@ -492,5 +492,8 @@ "openVip": "Open Now", "renewal": "Renewal", "daily": "Daily" + }, + "news": { + "newsDetail": "News Detail" } } diff --git a/locale/zh-Hans.json b/locale/zh-Hans.json index 9a12652..ab4ca29 100644 --- a/locale/zh-Hans.json +++ b/locale/zh-Hans.json @@ -492,5 +492,8 @@ "openVip": "立即开通", "renewal": "续费", "daily": "日均" + }, + "news": { + "newsDetail": "新闻详情" } } diff --git a/pages.json b/pages.json index 0a8d423..fa4f305 100644 --- a/pages.json +++ b/pages.json @@ -193,6 +193,12 @@ "navigationStyle": "custom", "navigationBarTitleText": "%order.orderDetails%" } + }, { + "path": "pages/news/details", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "新闻详情" + } }, { "path": "uni_modules/uni-upgrade-center-app/pages/upgrade-popup", "style": { diff --git a/pages/book/detail.vue b/pages/book/detail.vue index 1c16ead..acab442 100644 --- a/pages/book/detail.vue +++ b/pages/book/detail.vue @@ -264,7 +264,7 @@ function handlePurchase(goods: IGoods) { // 页面跳转 function goToReader() { - const isBuy = bookInfo.value.isBuy ? 0 : 1 + const isBuy = bookInfo.value.isBuy ? 1 : 0 const count = bookInfo.value.freeChapterCount || 0 uni.navigateTo({ url: `/pages/book/reader?isBuy=${isBuy}&bookId=${bookId.value}&count=${count}` diff --git a/pages/book/index.vue b/pages/book/index.vue index 9a7e60b..cc56798 100644 --- a/pages/book/index.vue +++ b/pages/book/index.vue @@ -329,7 +329,7 @@ const handleSearch = ({ value }: { value: string }) => { */ const handleMyBookClick = (bookId: number) => { uni.navigateTo({ - url: `/pages/book/reader?isBuy=0&bookId=${bookId}` + url: `/pages/book/reader?isBuy=1&bookId=${bookId}` }) } diff --git a/pages/book/listen/index.vue b/pages/book/listen/index.vue index 85261d5..67e2bdb 100644 --- a/pages/book/listen/index.vue +++ b/pages/book/listen/index.vue @@ -19,7 +19,7 @@ v-if="!bookInfo.isBuy" type="primary" size="small" - @click="goToPurchase" + @click="purchaseVisible = true" > {{ $t('bookDetails.buy') }} @@ -42,13 +42,21 @@ {{ chapter.chapter }}{{ chapter.content ? ' - ' + chapter.content : '' }} - + {{ nullText }} + + + @@ -58,7 +66,8 @@ import { onLoad } from '@dcloudio/uni-app' import { useI18n } from 'vue-i18n' import { bookApi } from '@/api/modules/book' import type { IBookDetail, IChapter } from '@/types/book' -import CustomNavbar from '@/components/book/CustomNavbar.vue' +import type { IGoods } from '@/types/order' +import GoodsSelector from '@/components/order/GoodsSelector.vue' const { t } = useI18n() @@ -80,11 +89,6 @@ const activeIndex = ref(-1) const nullText = ref('') const scrollHeight = ref(0) -// 计算属性 -const isLocked = computed(() => (index: number) => { - return !bookInfo.value.isBuy && index + 1 > bookInfo.value.freeChapterCount -}) - // 生命周期 onLoad((options: any) => { if (options.bookId) { @@ -98,8 +102,31 @@ onLoad((options: any) => { initScrollHeight() loadBookInfo() loadChapterList() + loadGoodsInfo() }) +// 购买弹窗状态 +const purchaseVisible = ref(false) +const goodsList = ref([]) + +// 关闭购买弹窗 +function closePurchasePopup() { + purchaseVisible.value = false +} +// 确认购买 +function handlePurchase(goods: IGoods) { + uni.navigateTo({ + url: `/pages/order/goodsConfirm?goods=${goods.productId}` + }) +} + + +// 加载购买商品信息 +async function loadGoodsInfo() { + const res = await bookApi.getBookGoods(bookId.value) + goodsList.value = res.productList || [] +} + // 初始化滚动区域高度 function initScrollHeight() { const systemInfo = uni.getSystemInfoSync() @@ -134,10 +161,15 @@ async function loadChapterList() { } } +// 判断章节是否锁定 +function isLocked(index: number): boolean { + return !bookInfo.value.isBuy && index + 1 > bookInfo.value.freeChapterCount +} + // 播放章节 function playChapter(chapter: IChapter, index: number) { // 检查是否锁定 - if (isLocked.value(index)) { + if (isLocked(index)) { uni.showToast({ title: t('book.afterPurchase'), icon: 'none' diff --git a/pages/book/reader.vue b/pages/book/reader.vue index 038ba87..b804534 100644 --- a/pages/book/reader.vue +++ b/pages/book/reader.vue @@ -111,7 +111,7 @@ {{ chapter.chapter }}{{ chapter.content ? ' - ' + chapter.content : '' }} - + @@ -201,7 +201,7 @@ const bookStore = useBookStore() // 路由参数 const bookId = ref(0) -const isBuy = ref('0') +const isBuy = ref(false) const count = ref(0) // 数据状态 @@ -273,7 +273,7 @@ const currentChapterTitle = computed(() => { onLoad((options: any) => { if (options.bookId) bookId.value = Number(options.bookId) - if (options.isBuy) isBuy.value = options.isBuy + if (options.isBuy) isBuy.value = options.isBuy == 1 ? true : false if (options.count) count.value = Number(options.count) // 获取刘海高度 @@ -430,7 +430,7 @@ async function switchChapter(chapter: IChapter, index: number) { // 判断章节是否锁定 function isLocked(index: number): boolean { - return isBuy.value === '1' && index + 1 > count.value + return !isBuy.value && index + 1 > count.value } // 判断是否是图片 diff --git a/pages/book/search.vue b/pages/book/search.vue index 1bf1b0e..497f7dd 100644 --- a/pages/book/search.vue +++ b/pages/book/search.vue @@ -27,12 +27,7 @@ > {{ item.name }} - {{ - formatPrice(item) - }} - {{ - formatStats(item) - }} + @@ -47,6 +42,7 @@ import { ref, onMounted } from 'vue' import { onLoad } from '@dcloudio/uni-app' import { useI18n } from 'vue-i18n' import { homeApi } from '@/api/modules/book_home' +import BookPrice from '@/components/book/BookPrice.vue' import type { IBookWithStats, IVipInfo } from '@/types/home' const { t } = useI18n() @@ -260,21 +256,9 @@ onMounted(async () => { 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; } } } diff --git a/pages/course/details/chapter.vue b/pages/course/details/chapter.vue index 014bdfa..537d86b 100644 --- a/pages/course/details/chapter.vue +++ b/pages/course/details/chapter.vue @@ -31,7 +31,7 @@ {{ $t('courseDetails.videoTeaching') }} - + 【{{ video.type == "2" ? $t('courseDetails.audio') : $t('courseDetails.video') }}】{{ index + 1 }} diff --git a/pages/course/details/components/CatalogueList.vue b/pages/course/details/components/CatalogueList.vue new file mode 100644 index 0000000..de9e50d --- /dev/null +++ b/pages/course/details/components/CatalogueList.vue @@ -0,0 +1,450 @@ + + + + + diff --git a/components/course/CourseInfo.vue b/pages/course/details/components/CourseInfo.vue similarity index 100% rename from components/course/CourseInfo.vue rename to pages/course/details/components/CourseInfo.vue diff --git a/pages/course/details/course.vue b/pages/course/details/course.vue index c10c816..067cd5d 100644 --- a/pages/course/details/course.vue +++ b/pages/course/details/course.vue @@ -16,40 +16,23 @@ - - - - - - - + {{ $t('courseDetails.progress') }} - + @@ -166,30 +149,24 @@ + + \ No newline at end of file diff --git a/pages/user/myBook/index.vue b/pages/user/myBook/index.vue index 75ac5da..3142c46 100644 --- a/pages/user/myBook/index.vue +++ b/pages/user/myBook/index.vue @@ -68,7 +68,7 @@ function goToDetail(bookId: number) { function goToReader(bookId: number) { uni.navigateTo({ - url: `/pages/book/reader?isBuy=0&bookId=${bookId}` + url: `/pages/book/reader?isBuy=1&bookId=${bookId}` }) } diff --git a/pages/user/profile/index.vue b/pages/user/profile/index.vue index 6ca570c..6d54bd7 100644 --- a/pages/user/profile/index.vue +++ b/pages/user/profile/index.vue @@ -7,7 +7,7 @@ @@ -148,7 +148,7 @@ const { t } = useI18n() const userStore = useUserStore() // 默认头像 -const defaultAvatar = '/static/home_icon.png' +const defaultAvatar = '/static/logo.png' // 字段列表 const fields = computed(() => [ @@ -200,10 +200,9 @@ const avatarUrl = ref('') const userInfo = ref({}) // 用户信息 const getData = async () => { const res = await getUserInfo() - if (res.result) { - userStore.setUserInfo(res.result) - userInfo.value = res.result - } + userStore.setUserInfo(res.result) + userInfo.value = res.result + userInfo.value.avatar = res.result.avatar || defaultAvatar } /** diff --git a/static/nobg.jpg b/static/nobg.jpg new file mode 100644 index 0000000..e2580cd Binary files /dev/null and b/static/nobg.jpg differ diff --git a/stores/course.ts b/stores/course.ts deleted file mode 100644 index b38dc26..0000000 --- a/stores/course.ts +++ /dev/null @@ -1,142 +0,0 @@ -// stores/course.ts -import { defineStore } from 'pinia' -import { courseApi } from '@/api/modules/course' -import type { ICourseDetail, ICatalogue, IChapter, IVipInfo } from '@/types/course' - -interface CourseState { - currentCourse: ICourseDetail | null - catalogueList: ICatalogue[] - currentCatalogueIndex: number - chapterList: IChapter[] - userVip: IVipInfo | null - learningProgress: number -} - -export const useCourseStore = defineStore('course', { - state: (): CourseState => ({ - currentCourse: null, - catalogueList: [], - currentCatalogueIndex: 0, - chapterList: [], - userVip: null, - learningProgress: 0, - }), - - getters: { - /** - * 获取当前选中的目录 - */ - currentCatalogue: (state): ICatalogue | null => { - if (state.catalogueList.length === 0) return null - return state.catalogueList[state.currentCatalogueIndex] || null - }, - - /** - * 判断当前目录是否已购买 - */ - isCurrentCataloguePurchased: (state): boolean => { - const catalogue = state.catalogueList[state.currentCatalogueIndex] - return catalogue ? catalogue.isBuy === 1 : false - }, - - /** - * 判断用户是否为VIP - */ - isVip: (state): boolean => { - return state.userVip !== null - }, - }, - - actions: { - /** - * 获取课程详情 - * @param courseId 课程ID - */ - async fetchCourseDetail(courseId: number) { - try { - const res = await courseApi.getCourseDetail(courseId) - if (res.code === 0 && res.data) { - this.currentCourse = res.data.course - this.catalogueList = res.data.catalogues || [] - - // 计算学习进度 - if (this.catalogueList.length > 0) { - const totalProgress = this.catalogueList.reduce((sum, cat) => sum + cat.completion, 0) - this.learningProgress = Number((totalProgress / this.catalogueList.length).toFixed(2)) - } else { - this.learningProgress = 0 - } - } - return res - } catch (error) { - console.error('获取课程详情失败:', error) - throw error - } - }, - - /** - * 切换目录 - * @param index 目录索引 - */ - async switchCatalogue(index: number) { - if (index < 0 || index >= this.catalogueList.length) { - console.warn('目录索引超出范围') - return - } - - this.currentCatalogueIndex = index - const catalogue = this.catalogueList[index] - - // 获取该目录的章节列表 - await this.fetchChapterList(catalogue.id) - }, - - /** - * 获取章节列表 - * @param catalogueId 目录ID - */ - async fetchChapterList(catalogueId: number) { - try { - const res = await courseApi.getCatalogueChapterList(catalogueId) - if (res.code === 0) { - this.chapterList = res.chapterList || [] - } - return res - } catch (error) { - console.error('获取章节列表失败:', error) - this.chapterList = [] - throw error - } - }, - - /** - * 检查用户VIP权益 - * @param courseId 课程ID - */ - async checkVipStatus(courseId: number) { - try { - const res = await courseApi.checkCourseVip(courseId) - if (res.code === 0) { - this.userVip = res.userVip || null - } - return res - } catch (error) { - console.error('检查VIP权益失败:', error) - this.userVip = null - throw error - } - }, - - /** - * 重置课程状态 - */ - resetCourseState() { - this.currentCourse = null - this.catalogueList = [] - this.currentCatalogueIndex = 0 - this.chapterList = [] - this.userVip = null - this.learningProgress = 0 - }, - }, -}) diff --git a/style/tailwind.css b/style/tailwind.css index f87c03a..4566267 100644 --- a/style/tailwind.css +++ b/style/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-white: #fff; --spacing: 0.25rem; --text-xs: 0.75rem; --text-xs--line-height: calc(1 / 0.75); @@ -212,18 +211,12 @@ max-width: 96rem; } } - .mr-1 { - margin-right: calc(var(--spacing) * 1); - } - .ml-1 { - margin-left: calc(var(--spacing) * 1); + .mb-2\! { + margin-bottom: calc(var(--spacing) * 2) !important; } .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; } @@ -251,24 +244,6 @@ .table { display: table; } - .h-20 { - height: calc(var(--spacing) * 20); - } - .h-\[80px\] { - height: 80px; - } - .h-\[80rpx\] { - height: 80rpx; - } - .w-20 { - width: calc(var(--spacing) * 20); - } - .w-\[80px\] { - width: 80px; - } - .w-\[80rpx\] { - width: 80rpx; - } .w-\[100px\] { width: 100px; } @@ -278,9 +253,6 @@ .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,); } @@ -290,34 +262,10 @@ .flex-wrap { flex-wrap: wrap; } - .items-center { - align-items: center; - } - .justify-center { - justify-content: center; - } - .rounded-full { - border-radius: calc(infinity * 1px); - } .border { border-style: var(--tw-border-style); border-width: 1px; } - .bg-\[blue\] { - background-color: blue; - } - .bg-\[red\] { - background-color: red; - } - .bg-\[transparent\] { - background-color: transparent; - } - .bg-white { - background-color: var(--color-white); - } - .p-0 { - padding: calc(var(--spacing) * 0); - } .p-0\! { padding: calc(var(--spacing) * 0) !important; } @@ -327,12 +275,6 @@ .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; } @@ -354,32 +296,13 @@ font-size: var(--text-xs); line-height: var(--tw-leading, var(--text-xs--line-height)); } - .font-\[26rpx\] { - --tw-font-weight: 26rpx; - font-weight: 26rpx; - } .font-bold { --tw-font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold); } - .text-\[\#000\] { - color: #000; - } .text-\[\#7dc1f0\] { color: #7dc1f0; } - .text-\[\#fff\] { - color: #fff; - } - .text-\[\'10px\'\] { - color: '10px'; - } - .text-\[\'12px\'\] { - color: '12px'; - } - .text-\[26rpx\] { - color: 26rpx; - } .text-\[cadetblue\] { color: cadetblue; } @@ -399,17 +322,6 @@ --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-\[0_0_10px_rgba\(0\,0\,0\,0\.1\)\] { - --tw-shadow: 0 0 10px var(--tw-shadow-color, rgba(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); - } - .shadow-\[0_0_10px_rgba\(0\,0\,0\,0\.05\)\] { - --tw-shadow: 0 0 10px var(--tw-shadow-color, rgba(0,0,0,0.05)); - 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); diff --git a/style/ui.scss b/style/ui.scss index 940e4b8..7e71e68 100644 --- a/style/ui.scss +++ b/style/ui.scss @@ -109,10 +109,10 @@ uni-textarea { // popup .wd-popup { - z-index: 9999 !important; + z-index: 99 !important; } .wd-overlay { - z-index: 9998 !important; + z-index: 98 !important; } .wd-popup-wrapper { .wd-popup {