Browse Source

'几乎联调完毕!'

hfll
hflllll 1 month ago
parent
commit
fe2c9464ed
22 changed files with 1608 additions and 308 deletions
  1. +5
    -4
      api/index.js
  2. +19
    -1
      api/modules/book.js
  3. +21
    -1
      api/modules/member.js
  4. +14
    -0
      api/modules/music.js
  5. +31
    -0
      api/modules/promotion.js
  6. +0
    -24
      api/modules/user.js
  7. +12
    -0
      pages.json
  8. +190
    -133
      pages/index/member.vue
  9. +1
    -1
      pages/index/user.vue
  10. BIN
      static/知识收获图标.png
  11. BIN
      static/视频封面.png
  12. +2
    -1
      stores/index.js
  13. +716
    -85
      subPages/home/book.vue
  14. +6
    -5
      subPages/home/directory.vue
  15. +188
    -0
      subPages/home/music.vue
  16. +122
    -31
      subPages/member/recharge.vue
  17. +22
    -2
      subPages/user/cash.vue
  18. +27
    -4
      subPages/user/discount.vue
  19. +43
    -14
      subPages/user/promote.vue
  20. +106
    -0
      subPages/user/share.vue
  21. +78
    -1
      utils/common.js
  22. +5
    -1
      utils/index.js

+ 5
- 4
api/index.js View File

@ -1,18 +1,19 @@
import user from '@/api/modules/user'
// import user from '@/api/modules/user'
import config from '@/api/modules/config'
import login from '@/api/modules/login'
import home from '@/api/modules/home'
import member from '@/api/modules/member'
import book from '@/api/modules/book'
import promotion from '@/api/modules/promotion'
import music from '@/api/modules/music'
export {
user,
// user,
config,
login,
home,
member,
book,
promotion
promotion,
music
}

+ 19
- 1
api/modules/book.js View File

@ -87,5 +87,23 @@ export default {
method: "GET",
data
})
}
},
// 获取课程页面详情
async coursesPageDetail(data){
return request({
url: "/books/coursesPageDetail",
method: "GET",
data
})
},
// 获取课程页面列表
async coursePage(data){
return request({
url: "/books/coursePage",
method: "GET",
data
})
},
}

+ 21
- 1
api/modules/member.js View File

@ -28,5 +28,25 @@ export default {
needToken: true,
data
})
}
},
// 开通会员
async openMember(data) {
return request({
url: '/member/open',
method: 'POST',
data,
needToken: true,
showLoading: true
})
},
// 获取当前用户会员信息
async getUserMemberInfo() {
return request({
url: '/member/userMemberInfo',
method: 'GET',
needToken: true
})
},
}

+ 14
- 0
api/modules/music.js View File

@ -0,0 +1,14 @@
import request from '@/api/request'
export default{
// 查询音色列表
async list(){
return request({
url: "/tts/list",
method: "GET",
needToken: true
})
}
}

+ 31
- 0
api/modules/promotion.js View File

@ -19,5 +19,36 @@ export default {
data,
needToken: true
})
},
// 提现
async withdraw(data){
return request({
url: "/promotion/withdraw",
method: "POST",
data,
needToken: true,
showLoading: true,
})
},
// 获取推广统计
async statistics(data){
return request({
url: "/promotion/statistics",
method: "GET",
data,
needToken: true
})
},
async qrCode(data){
return request({
url: "/promotion/qrCode",
method: "GET",
data,
needToken: true
})
}
}

+ 0
- 24
api/modules/user.js View File

@ -1,24 +0,0 @@
// import request from "@/api/request";
import http from "@/api/http";
export default {
// 我的资料- 获取个人信息
async queryUser() {
return http({
url: '/userInfo/queryUser',
method: 'GET',
needToken: true
})
},
// 我的资料- 修改个人信息
async updateUser(data) {
return http({
url: '/userInfo/updateUser',
method: 'POST',
data,
needToken: true
})
},
}

+ 12
- 0
pages.json View File

@ -125,6 +125,18 @@
"style": {
"navigationStyle": "custom"
}
},
{
"path": "home/music",
"style": {
"navigationBarTitleText": "音乐切换"
}
},
{
"path": "user/share",
"style": {
"navigationBarTitleText": "分享"
}
}
]
}


+ 190
- 133
pages/index/member.vue View File

@ -18,62 +18,64 @@
class="zuanshi-img"
/>
</view>
<image
src="/static/VIP.png"
mode="aspectFit"
class="VIP-img"
/>
<text class="intro">共19项会员特权 | 3 项年VIP专属特权</text>
<view class="border" />
<view class="info">
<view class="avatar-box">
<image
:src="userInfo.avatar"
mode="aspectFill"
class="avatar"
/>
<text class="name">{{userInfo.name}}</text>
<view v-if="!isMember" class="noVip-container">
<image
src="/static/VIP.png"
mode="aspectFit"
class="VIP-img"
/>
<text class="intro" >共19项会员特权 | 3 项年VIP专属特权</text>
<view class="border" ></view>
<view class="info" >
<view class="avatar-box">
<image
:src="userInfo.avatar"
mode="aspectFill"
class="avatar"
/>
<text class="name">{{userInfo.name}}</text>
</view>
<uv-button :text="isLogin ? '立即开通' : '前往登陆'"
@click="goRecharge"
type="primary"
:customStyle="{
width: '160rpx',
height: '60rpx',
borderRadius: '198rpx',
backgroundColor: '#06DADC',
color: '#fff',
fontSize: '28rpx',
fontWeight: '500',
lineHeight: '60rpx',
letterSpacing: '0%',
verticalAlign: 'middle',
}" />
</view>
<uv-button text="立即开通"
@click="goRecharge"
type="primary"
:customStyle="{
width: '160rpx',
height: '60rpx',
borderRadius: '198rpx',
backgroundColor: '#06DADC',
color: '#fff',
fontSize: '28rpx',
fontWeight: '500',
lineHeight: '60rpx',
letterSpacing: '0%',
verticalAlign: 'middle',
}" />
</view>
<view v-else>
<!-- 代码加在这里 -->
<view class="vip-container " >
<view class="avatar-box ">
<image
:src="userInfo.avatar"
mode="aspectFill"
class="avatar"
/>
<text class="name">{{userInfo.name}}</text>
<text class="time">{{memberInfo[0].endTime.split(' ')[0]}}</text>
</view>
<view class="border" ></view>
<view class="project">{{ memberInfo[0].memberTitle }}</view>
<text class="res-time">{{ '预计剩余学习' + $utils.calculateDateDifference(memberInfo[0].endTime.split(' ')[0]) + '天'}}</text>
</view>
</view>
</view>
</view>
<!-- 立即开通会员按钮 -->
<view class="member-button-section">
<uv-button
type="primary"
text="立即开通会员"
:custom-style="{
width: '100%',
height: '82rpx',
borderRadius: '44rpx',
backgroundColor: '#06DADC',
fontSize: '36rpx',
fontWeight: '500',
border: '1px solid #06DADC'
}"
@click="goRecharge"
></uv-button>
</view>
<!-- 会员权益 -->
<view class="benefits-section">
<view class="benefits-section" v-if="!isMember">
<view class="benefits-title">会员权益</view>
<view class="benefits-list">
@ -115,7 +117,7 @@
<!-- 以下内容为成为会员才能看到的 -->
<!-- 学习计划 -->
<view class="study-plan-section">
<view class="study-plan-section" v-else>
<view class="section-title">学习计划</view>
<view class="plan-books">
@ -123,10 +125,11 @@
v-for="(book, index) in studyPlanBooks"
:key="index"
class="plan-book-item"
@click="goBookDetail(book.book.id)"
:class="{ 'active-book': index === 1 }"
>
<view class="plan-book-cover">
<image :src="book.cover" mode="aspectFill"></image>
<image :src="book.book.booksImg" mode="aspectFill"></image>
<!-- 学习中标识 -->
<view v-if="index === 1" class="studying-badge">
<view class="studying-icon"/>
@ -134,12 +137,12 @@
</view>
</view>
<view class="plan-book-info">
<text class="plan-book-title" :class="{ 'highlight-title': index === 1 }">{{ book.title }}</text>
<text class="plan-book-title" :class="{ 'highlight-title': index === 1 }">{{ book.book.booksName }}</text>
<view class="plan-book-meta" >
<text class="plan-book-grade" :class="{ 'highlight-title': index === 1 }">{{ book.grade }}/</text>
<text class="plan-book-grade" :class="{ 'highlight-title': index === 1 }">{{ book.book.categoryName }}/</text>
<image v-if="index !== 1" src="/static/播放图标.png" class="plan-book-duration-icon" />
<image v-else src="/static/播放图标高亮.png" class="plan-book-duration-icon" />
<text class="plan-book-duration" :class="{ 'highlight-title': index === 1 }">{{ book.duration }}</text>
<text class="plan-book-duration" :class="{ 'highlight-title': index === 1 }">{{ book.book.duration }}</text>
</view>
</view>
</view>
@ -147,8 +150,8 @@
</view>
<!-- 学习推荐 -->
<view class="study-recommend-section">
<view class="section-header">
<view class="study-recommend-section" v-if="isMember">
<view class="section-header" @click="goRecommend">
<text class="section-title">学习推荐</text>
<view class="section-more">
<text>更多</text>
@ -160,15 +163,16 @@
<view
v-for="(book, index) in recommendBooks"
:key="index"
@click="goBookDetail(book.id)"
class="recommend-grid-item"
>
<view class="recommend-grid-cover">
<image :src="book.cover" mode="aspectFill"></image>
<image :src="book.booksImg" mode="aspectFill"></image>
</view>
<view class="recommend-grid-info">
<text class="recommend-grid-title">{{ book.title }}</text>
<text class="recommend-grid-title">{{ book.booksName }}</text>
<view class="recommend-grid-meta">
<text class="recommend-grid-grade">{{ book.grade }}/</text>
<text class="recommend-grid-grade">{{ book.categoryName }}/</text>
<image src="/static/播放图标.png" class="recommend-grid-duration-icon" />
<text class="recommend-grid-duration">{{ book.duration }}</text>
</view>
@ -184,6 +188,8 @@
export default{
data() {
return {
isLogin: uni.getStorageSync('token') ? true : false,
memberInfo: [],
userInfo: {
name: '战斗世界',
avatar: '/static/默认头像.png'
@ -191,76 +197,96 @@ export default{
//
studyPlanBooks: [
{
cover: '/static/默认图片.png',
title: '精讲短文',
grade: '四级',
duration: '03:24'
},
{
cover: '/static/默认图片.png',
title: '精讲短文',
grade: '四级',
duration: '03:24'
},
{
cover: '/static/默认图片.png',
title: '精讲短文',
grade: '四级',
duration: '03:24'
}
],
//
recommendBooks: [
{
cover: '/static/默认图片.png',
title: '小王子',
grade: '四级',
duration: '03:24'
},
{
cover: '/static/默认图片.png',
title: '自私的巨人',
grade: '四级',
duration: '03:24'
},
{
cover: '/static/默认图片.png',
title: '百万英镑',
grade: '四级',
duration: '03:24'
},
{
cover: '/static/默认图片.png',
title: 'MATILDA',
grade: '四级',
duration: '03:24'
},
{
cover: '/static/默认图片.png',
title: 'Pride and Prejudice',
grade: '四级',
duration: '03:24'
},
{
cover: '/static/默认图片.png',
title: '温德尔·范·德拉南',
grade: '四级',
duration: '03:24'
}
]
}
},
computed: {
isMember() {
return this.memberInfo.length > 0
}
},
methods: {
//
goRecommend() {
uni.navigateTo({
url: '/subPages/home/search'
})
},
//
goBookDetail(bookId) {
uni.navigateTo({
url: `/subPages/home/directory?id=${bookId}`
})
},
goRecharge() {
if (!this.isLogin) {
uni.navigateTo({
url: '/subPages/login/login'
})
return
}
uni.navigateTo({
url: '/subPages/member/recharge'
})
},
//
async getUserMemberInfo() {
const memberRes = await this.$api.member.getUserMemberInfo()
if (memberRes.code === 200) {
this.memberInfo = [...memberRes.result]
}
},
//
async getUserInfo() {
const userRes = await this.$api.login.getUserInfo()
if (userRes.code === 200) {
this.userInfo = userRes.result
}
},
// 3
async getStudyPlanBook() {
const bookRes = await this.$api.book.stand({
pageNo: 1,
pageSize: 3
})
if (bookRes.code === 200) {
const oneBook = bookRes.result.records[1]
const twoBook = bookRes.result.records[0]
const threeBook = bookRes.result.records[2]
this.studyPlanBooks = [oneBook, twoBook, threeBook]
}
},
async getRecommendBook() {
const bookRes = await this.$api.book.list({
pageNo: 1,
pageSize: 6,
member: 1
}, false)
if (bookRes.code === 200) {
this.recommendBooks = bookRes.result.records
}
},
},
async onShow() {
//
if (uni.getStorageSync('token')) {
this.isLogin = true
Promise.all([this.getUserMemberInfo(), this.getUserInfo(), this.getStudyPlanBook(), this.getRecommendBook()])
}else {
this.isLogin = false
this.userInfo = {
name: '登录后查看会员情况',
avatar: '/static/默认头像.png'
}
}
}
}
</script>
@ -296,7 +322,7 @@ export default{
.header-content{
margin: 0 18rpx;
margin-top: -150rpx;
height: 256rpx;
// height: 256rpx;
border-radius: 32rpx;
border-width: 2rpx;
padding: 40rpx;
@ -306,6 +332,32 @@ export default{
flex-direction: column;
gap: 28rpx;
position: relative;
.vip-container{
padding: 20rpx 0;
display: flex;
flex-direction: column;
gap: 18rpx;
.project{
font-size: 36rpx;
color: #191919;
line-height: 1.4;
font-weight: 500;
}
.res-time{
color: $primary-color;
font-size: 24rpx;
line-height: 36rpx;
}
}
.noVip-container{
display: flex;
flex-direction: column;
// align-items: center;
// justify-content: center;
gap: 28rpx;
}
.zuanshi{
position: absolute;
width: 190rpx;
@ -343,25 +395,30 @@ export default{
display: flex;
justify-content: space-between;
align-items: center;
.avatar-box{
display: flex;
align-items: center;
gap: 16rpx;
.name{
font-weight: 600;
font-size: 36rpx;
line-height: 44rpx;
letter-spacing: 0%;
vertical-align: middle;
color: #252545;
}
.avatar{
width: 60rpx;
height: 60rpx;
border-radius: 50%;
}
}
.avatar-box{
display: flex;
align-items: center;
gap: 16rpx;
.name{
font-weight: 600;
font-size: 36rpx;
line-height: 44rpx;
letter-spacing: 0%;
vertical-align: middle;
color: #252545;
}
.avatar{
width: 60rpx;
height: 60rpx;
border-radius: 50%;
}
.time{
font-size: 30rpx;
color: #8B8B8B;
line-height: 36rpx;
}
}
}
}
/* 立即开通会员按钮样式 */


+ 1
- 1
pages/index/user.vue View File

@ -43,7 +43,7 @@
<view class="menu-list">
<!-- 推广中心 -->
<view class="menu-item" @click="goToPromotion">
<view class="menu-item" @click="goToPromotion" v-if="userInfo.isPromote === 'Y'">
<view class="menu-left">
<image src="/static/推广图标.png" class="menu-icon"></image>
<text class="menu-title">推广中心</text>


BIN
static/知识收获图标.png View File

Before After
Width: 48  |  Height: 48  |  Size: 3.4 KiB

BIN
static/视频封面.png View File

Before After
Width: 670  |  Height: 376  |  Size: 240 KiB

+ 2
- 1
stores/index.js View File

@ -9,7 +9,8 @@ const store = new Vuex.Store({
// 存放状态
configList: [],
departmentList: [],
categoryList: []
categoryList: [],
},
mutations: {
// 构造用于uv-picker的树状结构数组


+ 716
- 85
subPages/home/book.vue View File

@ -8,10 +8,8 @@
<view class="navbar-left" @click="goBack">
<uv-icon name="arrow-left" size="20" color="#262626"></uv-icon>
</view>
<view class="navbar-title">{{ bookTitle }}</view>
<view class="navbar-right" @click="showMenu">
<uv-icon name="more-dot-fill" size="20" color="#262626"></uv-icon>
</view>
<view class="navbar-title">{{ currentPageTitle }}</view>
</view>
</view>
@ -20,7 +18,6 @@
class="content-swiper"
:current="currentPage - 1"
@change="onSwiperChange"
>
<swiper-item
v-for="(page, index) in bookPages"
@ -29,61 +26,197 @@
>
<view class="content-area" @click="toggleNavbar">
<!-- 图片卡片页面 -->
<view v-if="page.type === 'card'" class="card-content">
<view class="card-line">
<image src="/static/划重点图标.png" class="card-line-image" mode="aspectFill" />
<text class="card-line-text">划线重点</text>
<view v-for="(item, index) in page" :key="index">
<view v-if="item.type === 'card'" class="card-content">
<view class="card-line">
<image src="/static/划重点图标.png" class="card-line-image" mode="aspectFill" />
<text class="card-line-text">划线重点</text>
</view>
<image class="card-image" :src="item.image" mode="aspectFill"></image>
<text class="english-text" user-select @longpress="showWordMeaning(item.wordMeaning)">{{ item.englishText }}</text>
<text class="chinese-text" user-select>{{ item.chineseText }}</text>
</view>
<!-- 文本页面 -->
<view v-else-if="item.type === 'text'" class="text-content">
<text class="content-text" user-select>{{ item.content }}</text>
</view>
<!-- 文本页面 -->
<view v-else-if="item.type === 'image'" class="image-container">
<image class="content-image" :src="item.imageUrl" mode="aspectFill"></image>
</view>
<image class="card-image" :src="page.image" mode="aspectFill"></image>
<text class="english-text">{{ page.englishText }}</text>
<text class="chinese-text">{{ page.chineseText }}</text>
</view>
<!-- 文本页面 -->
<view v-else-if="page.type === 'text'" class="text-content">
<text class="content-text">{{ page.content }}</text>
</view>
<!-- 视频页面 -->
<view v-else-if="page.type === 'video'" class="video-content">
<video :src="page.video" class="video-player" controls></video>
<!-- 视频页面 -->
<view v-else-if="item.type === 'video'" class="video-content">
<video :src="item.video" class="video-player" controls :poster="item.poster"></video>
</view>
<!-- 会员限制页面 -->
<view v-else-if="item.type === 'member'" class="member-content">
<text class="member-title">{{ item.title }}</text>
<view class="member-button" @click="unlockBook">
<text class="member-button-text">{{ item.buttonText }}</text>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
<!-- 自定义底部控制栏 -->
<view class="custom-tabbar" :class="{ 'tabbar-hidden': !showNavbar }">
<view class="tabbar-content">
<view class="tabbar-left">
<view class="tab-button" @click="toggleSound">
<image src="/static/课程图标.png" class="tab-icon" />
<text class="tab-text">课程</text>
</view>
<view class="tab-button" @click="toggleSound">
<image src="/static/音色切换图标.png" class="tab-icon" />
<text class="tab-text">音色切换</text>
<!-- 音频控制栏 -->
<view class="audio-controls" :class="{ 'audio-hidden': !isTextPage }">
<view class="audio-time">
<text class="time-text">{{ formatTime(currentTime) }}</text>
<view class="progress-container">
<view class="progress-bar">
<view class="progress-fill" :style="{ width: progressPercent + '%' }"></view>
<view class="progress-thumb" :style="{ left: progressPercent + '%' }"></view>
</view>
</view>
<text class="time-text">{{ formatTime(totalTime) }}</text>
</view>
<view class="tabbar-right">
<view class="page-controls">
<view class="page-numbers">
<view
v-for="(page, index) in bookPages"
:key="index"
class="page-number"
:class="{ 'active': (index + 1) === currentPage }"
@click="goToPage(index + 1)"
>
{{ index + 1 }}
<view class="audio-controls-row">
<view class="control-btn" @click="toggleLoop">
<uv-icon name="reload" size="20" :color="isLoop ? '#06DADC' : '#999'"></uv-icon>
<text class="control-text">循环</text>
</view>
<view class="control-btn" @click="previousPage">
<text class="control-text">上一页</text>
</view>
<view class="play-btn" @click="togglePlay">
<uv-icon :name="isPlaying ? 'pause-circle-fill' : 'play-circle-fill'" size="40" color="#666"></uv-icon>
</view>
<view class="control-btn" @click="nextPage">
<text class="control-text">下一页</text>
</view>
<view class="control-btn" @click="toggleSpeed">
<text class="control-text">{{ playSpeed }}x</text>
</view>
</view>
</view>
<view style="background-color: #fff;position: relative;z-index: 100" >
<view class="tabbar-content">
<view class="tabbar-left">
<view class="tab-button" @click="toggleCoursePopup">
<image src="/static/课程图标.png" class="tab-icon" />
<text class="tab-text">课程</text>
</view>
<view class="tab-button" @click="toggleSound">
<image src="/static/音色切换图标.png" class="tab-icon" />
<text class="tab-text">音色切换</text>
</view>
</view>
<view class="tabbar-right">
<view class="page-controls">
<view class="page-numbers">
<view
v-for="(page, index) in bookPages"
:key="index"
class="page-number"
:class="{ 'active': (index + 1) === currentPage }"
@click="goToPage(index + 1)"
>
{{ index + 1 }}
</view>
</view>
</view>
</view>
</view>
<uv-safe-bottom></uv-safe-bottom>
</view>
<uv-safe-bottom></uv-safe-bottom>
</view>
<!-- 课程选择弹出窗 -->
<uv-popup
mode="bottom"
ref="coursePopup"
round="32rpx"
bg-color="#f8f8f8"
>
<view class="course-popup">
<view class="popup-header">
<view>
<uv-icon name="arrow-down" color="black" size="20"></uv-icon>
</view>
<view class="popup-title">课程</view>
<view class="popup-title" @click="toggleSort">
倒序
</view>
</view>
<view class="course-list">
<view
v-for="(course, index) in displayCourseList"
:key="course.id"
class="course-item"
:class="{ 'active': course.id === currentCourse }"
@click="selectCourse(course.id)"
>
<view class="course-number " :class="{ 'highlight': course.id === currentCourse }">{{ String(course.index).padStart(2, '0') }}</view>
<view class="course-content">
<view class="course-english" :class="{ 'highlight': course.id === currentCourse }">{{ course.english }}</view>
<view class="course-chinese" :class="{ 'highlight': course.id === currentCourse }">{{ course.chinese }}</view>
</view>
</view>
</view>
</view>
</uv-popup>
<!-- 释义弹出窗 -->
<uv-popup
mode="bottom"
ref="meaningPopup"
round="32rpx"
bg-color="#FFFFFF"
:overlay="true"
>
<view class="meaning-popup" v-if="currentWordMeaning">
<view class="meaning-header">
<view class="close-btn" @click="closeMeaningPopup">
<text class="close-text">关闭</text>
</view>
<view class="meaning-title">释义</view>
<view style="width: 80rpx;"></view>
</view>
<view class="meaning-content">
<image class="meaning-image" src="/static/默认图片.png" mode="aspectFill"></image>
<view class="word-info">
<view class="word-main">
<text class="word-text">{{ currentWordMeaning.word }}</text>
</view>
<view class="phonetic-container">
<uv-icon name="volume-fill" size="16" color="#000"></uv-icon>
<text class="phonetic-text">{{ currentWordMeaning.phonetic }}</text>
</view>
<view class="word-meaning">
<text class="part-of-speech">{{ currentWordMeaning.partOfSpeech }}</text>
<text class="meaning-text">{{ currentWordMeaning.meaning }}</text>
</view>
</view>
<view class="knowledge-gain">
<view class="knowledge-header">
<image src="/static/知识收获图标.png" class="knowledge-icon" mode="aspectFill" />
<text class="knowledge-title">知识收获</text>
</view>
<text class="knowledge-content">{{ currentWordMeaning.knowledgeGain }}</text>
</view>
</view>
</view>
</uv-popup>
</view>
</template>
@ -91,52 +224,206 @@
export default {
data() {
return {
courseId: '',
showNavbar: true,
currentPage: 1,
bookTitle: '看,乔治!',
currentCourse: 1, //
currentWordMeaning: null, //
isReversed: false, //
//
isPlaying: false,
currentTime: 0,
totalTime: 317, // 5:17 = 317
isLoop: false,
playSpeed: 1.0,
speedOptions: [1.0, 1.25, 1.5, 2.0],
courseIdList: [],
bookTitle: '',
courseList: [
],
//
bookPages: [
{
type: 'card',
englishText: 'I ought to have judged by deeds and not by words.',
chineseText: '要对一个人下定论,不应听其言,而应观其行。',
image: '/static/画重点图片.png'
},
{
type: 'video',
video: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4'
},
{
type: 'text',
content: '索菲达以南几英里处,萨利和斯坦对着近旁河岸的斜坡流淌,水深且绿意盎然。水也很温暖,因为它在阳光下闪烁着,清澈黄色的沙子,才到达深深的池塘。河的一侧,金色的山丘斜坡向上蜿蜒,通往坚固多岩石的加比亚山脉,但在山谷一侧,水边排列着树木——柳树在春天新鲜而翠绿,它们的低垂接触处充满着各天的冰水残留物;还有枫树,它们现实的,白色的横枝条和树枝;吉他也是绿色的地塘,在河下游的河岸上,叶子能发出很大的啪啪声。傍晚时分,兔子们从灌木中出现,坐在沙子上,湖泊的平地上覆盖着沉重的夜晚迹,还有来自农场的狗的散布的垫子,以及前来暗黑中饮水的鹿的分裂形足迹。'
},
{
type: 'text',
content: '有一条穿过柳树和榆树之间的路,这条路从农场下来的男孩们踏得又硬又深;他们来深池游泳,也被那些上游运送货物公路上下来的流浪汉使得又硬又深。他们在水边停歇,在一棵巨大的榆树低垂的水平树枝前,有一个由许多火堆形成的灰烬堆,核桃被坐在上面的人留下光滑。'
}
]
],
//
pageTitles: [],
}
},
computed: {
displayCourseList() {
return this.isReversed ? [...this.courseList].reverse() : this.courseList;
},
//
isTextPage() {
const currentPageData = this.bookPages[this.currentPage - 1];
// currentPageData typetexttrue
return currentPageData && currentPageData.some(item => item.type === 'text');
},
//
progressPercent() {
return this.totalTime > 0 ? (this.currentTime / this.totalTime) * 100 : 0;
},
//
currentPageTitle() {
return this.pageTitles[this.currentPage - 1] || this.bookTitle;
}
},
methods: {
toggleNavbar() {
this.showNavbar = !this.showNavbar
},
goBack() {
uni.navigateBack()
},
showMenu() {
console.log('显示菜单')
toggleCoursePopup() {
if (this.$refs.coursePopup) {
this.$refs.coursePopup.open()
}
// console.log('123123123');
},
toggleSort() {
this.isReversed = !this.isReversed
},
selectCourse(courseId) {
this.currentCourse = courseId
if (this.$refs.coursePopup) {
this.$refs.coursePopup.close()
}
//
// console.log(':', courseId)
this.getCourseList(courseId)
},
showWordMeaning(wordMeaning) {
if (wordMeaning) {
this.currentWordMeaning = wordMeaning
if (this.$refs.meaningPopup) {
this.$refs.meaningPopup.open()
}
}
},
closeMeaningPopup() {
if (this.$refs.meaningPopup) {
this.$refs.meaningPopup.close()
}
this.currentWordMeaning = null
},
//
togglePlay() {
this.isPlaying = !this.isPlaying;
// /
},
toggleLoop() {
this.isLoop = !this.isLoop;
},
toggleSpeed() {
const currentIndex = this.speedOptions.indexOf(this.playSpeed);
const nextIndex = (currentIndex + 1) % this.speedOptions.length;
this.playSpeed = this.speedOptions[nextIndex];
},
async previousPage() {
if (this.currentPage > 1) {
this.currentPage--;
//
if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) {
await this.getBookPages(this.courseIdList[this.currentPage - 1]);
}
}
},
async nextPage() {
if (this.currentPage < this.bookPages.length) {
this.currentPage++;
//
if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) {
await this.getBookPages(this.courseIdList[this.currentPage - 1]);
}
}
},
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
},
toggleSound() {
console.log('音色切换')
uni.navigateTo({
url: '/subPages/home/music'
})
},
goToPage(page) {
unlockBook() {
console.log('解锁全书')
//
uni.navigateTo({
url: '/pages/index/member'
})
},
async goToPage(page) {
this.currentPage = page
console.log('跳转到页面:', page)
//
if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) {
await this.getBookPages(this.courseIdList[this.currentPage - 1]);
}
},
onSwiperChange(e) {
async onSwiperChange(e) {
this.currentPage = e.detail.current + 1
//
if (this.courseIdList[this.currentPage - 1] && this.bookPages[this.currentPage - 1].length === 0) {
await this.getBookPages(this.courseIdList[this.currentPage - 1]);
}
},
async getCourseList(id) {
const res = await this.$api.book.coursePage({
id: id
})
if (res.code === 200) {
this.courseIdList = res.result.map(item => item.id)
//
this.bookPages = this.courseIdList.map(() => [])
//
this.pageTitles = this.courseIdList.map(() => '')
//
if (this.courseIdList.length > 0) {
await this.getBookPages(this.courseIdList[0])
}
}
},
async getBookPages(id) {
const res = await this.$api.book.coursesPageDetail({
id: id
})
if (res.code === 200) {
// 使$set
//
if (this.currentPage - 1 < this.bookPages.length) {
this.$set(this.bookPages, this.currentPage - 1, JSON.parse(res.result.content))
//
this.$set(this.pageTitles, this.currentPage - 1, res.result.title || '')
}
}
},
//
async getCoursePageList (bookId) {
const res = await this.$api.book.course({
id: bookId
})
if (res.code === 200) {
this.courseList = res.result.records
//
this.courseList = this.courseList.map((item, index) => ({
...item,
index,
}))
}
}
},
async onLoad(args) {
this.courseId = args.courseId
//
await Promise.all([this.getCourseList(this.courseId), this.getCoursePageList(args.bookId)])
}
}
</script>
@ -181,10 +468,13 @@ export default {
}
.navbar-right {
justify-content: flex-end;
flex: 1;
}
.navbar-title {
transform: translateX(-50rpx);
flex: 1;
text-align: center;
font-family: PingFang SC;
@ -196,8 +486,8 @@ export default {
.content-swiper {
flex: 1;
height: calc(100vh - 200rpx);
margin-top: 100rpx;
height: calc(100vh - 100rpx);
// margin-top: 100rpx;
margin-bottom: 100rpx;
}
@ -208,6 +498,7 @@ export default {
.content-area {
flex: 1;
padding: 0 40rpx;
padding-top: 100rpx;
// background: linear-gradient(180deg, #DEFFFF 0%, #FBFEFF 22.65%, #F0FBFF 100%);
height: 100%;
box-sizing: border-box;
@ -254,36 +545,80 @@ export default {
// margin-bottom: 20rpx;
}
.english-text {
display: block;
display: block;
font-family: PingFang SC;
font-weight: 600;
font-size: 32rpx;
line-height: 48rpx;
color: #3B3D3D;
// margin-bottom: 16rpx;
}
.chinese-text {
display: block;
font-family: PingFang SC;
font-weight: 400;
font-size: 28rpx;
line-height: 48rpx;
color: #4F4F4F;
}
}
/* 会员限制页面样式 */
.member-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 90%;
background-color: #F8F8F8;
padding: 40rpx;
margin: -40rpx;
box-sizing: border-box;
}
.member-title {
font-family: PingFang SC;
font-weight: 600;
font-size: 32rpx;
line-height: 48rpx;
color: #3B3D3D;
// margin-bottom: 16rpx;
font-weight: 500;
font-size: 40rpx;
line-height: 1;
color: #6f6f6f;
text-align: center;
margin-bottom: 48rpx;
}
.member-button {
width: 670rpx;
height: 72rpx;
background: #06DADC;
border-radius: 200rpx;
display: flex;
align-items: center;
justify-content: center;
}
.chinese-text {
display: block;
.member-button-text {
font-family: PingFang SC;
// font-weight: 400;
font-size: 28rpx;
line-height: 48rpx;
color: #4F4F4F;
font-weight: 400;
font-size: 30rpx;
color: #FFFFFF;
}
}
.video-content {
width: 100vw;
margin: 200rpx -40rpx 0;
height: 500rpx;
background-color: #FFFFFF;
padding: 40rpx;
// padding: 40rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
.video-player{
width: 670rpx;
margin: 0 auto;
height: 376rpx;
}
}
@ -313,7 +648,7 @@ export default {
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
// background-color: #fff;
border-top: 1rpx solid #EEEEEE;
z-index: 1000;
transition: transform 0.3s ease;
@ -328,6 +663,9 @@ export default {
align-items: center;
justify-content: space-between;
padding: 24rpx 62rpx;
// z-index: 100;
// position: relative;
// background-color: #fff;
// padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
height: 88rpx;
}
@ -401,4 +739,297 @@ export default {
color: $primary-color;
}
}
/* 课程弹出窗样式 */
.course-popup {
padding: 0 32rpx;
max-height: 80vh;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 2rpx solid #EEEEEE
// margin-bottom: 40rpx;
}
.popup-title {
font-family: PingFang SC;
font-weight: 500;
font-size: 34rpx;
color: #181818;
}
.course-list {
max-height: 60vh;
overflow-y: auto;
}
.course-item {
display: flex;
align-items: center;
gap: 24rpx;
padding-top: 24rpx;
padding-right: 8rpx;
padding-bottom: 24rpx;
padding-left: 8rpx;
border-bottom: 1px solid #EEEEEE;
cursor: pointer;
&:last-child {
border-bottom: none;
}
}
.course-number {
width: 80rpx;
font-family: PingFang SC;
// font-weight: 400;
font-size: 36rpx;
color: #999;
&.highlight {
color: $primary-color;
}
// margin-right: 24rpx;
}
.course-content {
flex: 1;
}
.course-english {
font-family: PingFang SC;
font-weight: 600;
font-size: 36rpx;
line-height: 44rpx;
color: #252545;
margin-bottom: 8rpx;
&.highlight {
color: $primary-color;
}
}
.course-chinese {
font-size: 28rpx;
line-height: 48rpx;
color: #3B3D3D;
&.highlight {
color: $primary-color;
}
}
/* 释义弹窗样式 */
.meaning-popup {
// width: 670rpx;
background-color: #FFFFFF;
// border-radius: 32rpx;
overflow: hidden;
}
.meaning-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 2rpx solid #EEEEEE;
}
.close-btn {
width: 80rpx;
}
.close-text {
font-family: PingFang SC;
font-size: 32rpx;
color: #8b8b8b;
}
.meaning-title {
font-family: PingFang SC;
font-weight: 500;
font-size: 34rpx;
color: #181818;
}
.meaning-content {
padding: 32rpx;
}
.meaning-image {
width: 670rpx;
height: 268rpx;
border-radius: 24rpx;
margin-bottom: 32rpx;
}
.word-info {
margin-bottom: 32rpx;
}
.word-main {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 8rpx;
}
.word-text {
font-family: PingFang SC;
font-weight: 500;
font-size: 40rpx;
color: #181818;
}
.phonetic-container{
display: flex;
gap: 24rpx;
.phonetic-text {
font-family: PingFang SC;
font-size: 28rpx;
color: #262626;
margin-bottom: 16rpx;
}
}
.word-meaning {
display: flex;
gap: 16rpx;
}
.part-of-speech {
font-family: PingFang SC;
font-size: 28rpx;
color: #262626;
}
.meaning-text {
font-family: PingFang SC;
font-size: 28rpx;
color: #181818;
flex: 1;
}
.knowledge-gain {
background: linear-gradient(180deg, #DEFFFF 0%, #FBFEFF 22.65%, #F0FBFF 100%);
border-radius: 24rpx;
padding: 32rpx 40rpx;
}
.knowledge-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 20rpx;
}
.knowledge-icon {
width: 48rpx;
height: 48rpx;
}
.knowledge-title {
font-family: PingFang SC;
font-weight: 600;
font-size: 30rpx;
color: #3B3D3D;
}
.knowledge-content {
font-family: PingFang SC;
font-size: 28rpx;
color: #4f4f4f;
line-height: 48rpx;
}
/* 音频控制栏样式 */
.audio-controls {
background: #fff;
padding: 20rpx 40rpx;
border-bottom: 1rpx solid #eee;
transition: transform 0.3s ease;
position: relative;
z-index: 10;
&.audio-hidden {
transform: translateY(100%);
}
}
.audio-time {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.time-text {
font-size: 28rpx;
color: #999;
min-width: 80rpx;
}
.progress-container {
flex: 1;
margin: 0 20rpx;
}
.progress-bar {
position: relative;
height: 6rpx;
background: #f0f0f0;
border-radius: 3rpx;
}
.progress-fill {
position: absolute;
left: 0;
top: 0;
height: 100%;
background: #06DADC;
border-radius: 3rpx;
transition: width 0.1s ease;
}
.progress-thumb {
position: absolute;
top: 50%;
width: 16rpx;
height: 16rpx;
background: #06DADC;
border-radius: 50%;
transform: translate(-50%, -50%);
transition: left 0.1s ease;
}
.audio-controls-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.control-btn {
display: flex;
// flex-direction: column;
align-items: center;
padding: 10rpx;
gap: 8rpx;
}
.control-text {
font-size: 28rpx;
color: #4A4A4A;
// margin-top: 8rpx;
}
.play-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 10rpx;
}
</style>

+ 6
- 5
subPages/home/directory.vue View File

@ -41,8 +41,8 @@
</view>
<view class="course-list">
<view
@click="startLearning()"
v-for="(course, index) in courseList.records"
v-for="(course, index) in courseList.records"
@click="startLearning(course.id)"
:key="index"
class="course-item"
>
@ -98,7 +98,7 @@
<image src="/static/内容图标.png" class="button-icon" ></image>
<text>内容朗读</text>
</view>
<uv-button @click="startLearning" type="primary" :custom-style="{
<uv-button @click="startLearning(courseList.records[0].id)" type="primary" :custom-style="{
width: '400rpx',
height: '80rpx',
borderRadius: '198rpx',
@ -149,9 +149,10 @@ export default {
}
},
//
startLearning() {
startLearning(id) {
//
uni.navigateTo({
url: '/subPages/home/book?id=' + this.id
url: '/subPages/home/book?courseId=' + id + '&bookId=' + this.id
})
},
//


+ 188
- 0
subPages/home/music.vue View File

@ -0,0 +1,188 @@
<template>
<view class="music-container">
<!-- 音色列表 -->
<view class="voice-list">
<view
v-for="(voice, index) in voiceList"
:key="voice.id"
class="voice-item"
:class="{ 'selected': voice.id === selectedVoiceId }"
@click="selectVoice(voice.id)"
>
<view class="voice-avatar">
<view class="play-icon">
<uv-icon
:name="voice.id === selectedVoiceId ? 'pause' : 'play-right-fill'"
size="24"
color="#666666"
></uv-icon>
</view>
</view>
<view class="voice-info">
<view class="voice-name">{{ voice.name }}</view>
<view class="voice-desc">{{ voice.info }}</view>
</view>
<view v-if="voice.id === selectedVoiceId" class="selected-tag">
已选择
</view>
</view>
</view>
<!-- 底部固定确认按钮 -->
<view class="bottom-bar">
<uv-button
type="primary"
text="确定"
:custom-style="{
width: '100%',
height: '82rpx',
borderRadius: '44rpx',
backgroundColor: '#06DADC',
fontSize: '36rpx',
fontWeight: '500',
border: '1px solid #06DADC'
}"
@click="confirmSelection"
></uv-button>
<uv-safe-bottom></uv-safe-bottom>
</view>
</view>
</template>
<script>
export default {
data() {
return {
selectedVoiceId: 2, //
voiceList: [
]
}
},
methods: {
goBack() {
uni.navigateBack()
},
selectVoice(voiceId) {
this.selectedVoiceId = voiceId
console.log('选择音色:', voiceId)
},
confirmSelection() {
console.log('确认选择音色:', this.selectedVoiceId)
//
uni.navigateBack()
},
async getVoice(){
const listRes = await this.$api.music.list()
if (listRes.code === 200) {
this.voiceList = listRes.result
}
}
},
onLoad() {
this.getVoice()
}
}
</script>
<style scoped lang="scss">
.music-container {
background: #fff;
min-height: 100vh;
display: flex;
flex-direction: column;
padding-bottom: 120rpx;
}
.voice-list {
flex: 1;
padding: 40rpx 32rpx 120rpx;
display: flex;
flex-direction: column;
gap: 32rpx;
}
.voice-item {
display: flex;
align-items: center;
padding: 32rpx 0;
border-bottom: 4rpx solid #F8F8F8;
position: relative;
background: #F8F8F8;
gap: 12px;
opacity: 1;
border-radius: 8px;
padding: 8px;
transition: all 0.3s ease;
&:last-child {
border-bottom: none;
}
&.selected {
border-bottom: 4rpx solid $primary-color;
}
}
.voice-avatar {
width: 136rpx;
height: 136rpx;
border-radius: 50%;
background: #666;
display: flex;
align-items: center;
justify-content: center;
// margin-right: 32rpx;
position: relative;
}
.play-icon {
display: flex;
width: 60rpx;
height: 60rpx;
border-radius: 100rpx;
background: white;
align-items: center;
justify-content: center;
}
.voice-info {
flex: 1;
}
.voice-name {
font-family: PingFang SC;
font-weight: 600;
font-size: 36rpx;
color: #262626;
margin-bottom: 8rpx;
}
.voice-desc {
font-family: PingFang SC;
font-weight: 400;
font-size: 28rpx;
color: #999;
}
.selected-tag {
background: $primary-color;
color: #fff;
font-size: 24rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-family: PingFang SC;
font-weight: 500;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 24rpx 50rpx;
z-index: 999;
border-top: 2rpx solid #F1F1F1
}
</style>

+ 122
- 31
subPages/member/recharge.vue View File

@ -74,12 +74,39 @@
<view class="coupon-section">
<view class="coupon-title">选择优惠券</view>
<view class="coupon-selector" @click="selectCoupon">
<text class="coupon-text">请选择</text>
<uv-icon name="arrow-right" color="#999" size="16" />
<view v-if="!selectedCoupon" class="coupon-placeholder">
<text class="coupon-text">请选择</text>
<uv-icon name="arrow-right" color="#999" size="16" />
</view>
<view v-else class="coupon-selected">
<view class="coupon-info">
<text class="coupon-name">{{ selectedCoupon.name }}</text>
<text class="coupon-amount">-¥{{ selectedCoupon.money }}</text>
</view>
<uv-icon name="arrow-right" color="#999" size="16" />
</view>
</view>
</view>
</view>
<!-- 立即开通会员按钮 -->
<view class="member-button-section">
<uv-button
type="primary"
text="立即开通会员"
:custom-style="{
width: '100%',
height: '82rpx',
borderRadius: '44rpx',
backgroundColor: '#06DADC',
fontSize: '36rpx',
fontWeight: '500',
border: '1px solid #06DADC'
}"
@click="handleRecharge"
></uv-button>
</view>
<!-- 会员权益 -->
<view class="benefits-section">
<view class="benefits-title">会员权益</view>
@ -134,28 +161,10 @@
selectedPackage: 0, //
selectedMember: 0, //
defaultPackageList: [
{
title: '当前会员没有返回套餐',
discountedprice: '36.00',
originalprice: '128',
gift: null,
content: '赠送66天'
},
{
title: '当前会员没有返回套餐',
discountedprice: '88.00',
originalprice: '384',
gift: '180',
content: '赠送666天'
},
{
title: '当前会员没有返回套餐',
discountedprice: '128.00',
originalprice: '384',
gift: null,
content: '赠送9999天'
}
]
],
couponId: '',
selectedCoupon: null //
}
},
computed:{
@ -163,7 +172,45 @@
return this.list[this.selectedMember].setmeals.length ? this.list[this.selectedMember].setmeals : this.defaultPackageList
}
},
methods: {
handleCouponSelected(coupon) {
//
this.selectedCoupon = coupon
this.couponId = coupon.id
uni.showToast({
title: `已选择优惠券:¥${coupon.money}`,
icon: 'success'
})
},
async handleRecharge(){
// console.log('id:', this.packageList[this.selectedPackage]);
const object = {
memberId: this.list[this.selectedMember].id,
setmealId: this.packageList[this.selectedPackage].id
}
if (this.couponId) {
object.couponId = this.couponId
}
const res = await this.$api.member.openMember({...object})
if (res.code === 200){
if (res.result === 0){
//
uni.showToast({
title: '充值成功',
icon: 'success'
})
this.goBack()
}else {
//
this.$utils.wxPay(res.result)
}
}
},
goBack(){
uni.navigateBack()
},
@ -188,9 +235,8 @@
this.selectedPackage = index
},
selectCoupon() {
uni.showToast({
title: '功能开发中',
icon: 'none'
uni.navigateTo({
url: '/subPages/user/discount?from=recharge'
})
},
async getMemberList(){
@ -205,8 +251,16 @@
}
},
onShow(){
//
uni.$on('couponSelected', this.handleCouponSelected)
this.getMemberList()
}
},
onUnload() {
//
uni.$off('couponSelected', this.handleCouponSelected)
console.log('接触监听');
},
}
</script>
@ -264,6 +318,11 @@
}
/* 立即开通会员按钮样式 */
.member-button-section {
margin: 0 50rpx 40rpx;
}
//
.membership-container {
background: linear-gradient(180deg, #DEFFFF 0%, #FBFEFF 22.65%, #FFFFFF 100%);
@ -370,9 +429,41 @@
align-items: center;
border-bottom: 2rpx solid #f0f0f0;
.coupon-text {
font-size: 32rpx;
color: #C6C6C6;
.coupon-placeholder {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
.coupon-text {
font-size: 32rpx;
color: #C6C6C6;
}
}
.coupon-selected {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
.coupon-info {
display: flex;
// flex-direction: column;
align-items: center;
justify-content: space-between;
.coupon-name {
font-size: 28rpx;
color: #181818;
margin-bottom: 4rpx;
}
.coupon-amount {
font-size: 28rpx;
color: red;
font-weight: 500;
}
}
}
}
}


+ 22
- 2
subPages/user/cash.vue View File

@ -25,7 +25,7 @@
<uv-input
v-model="amount"
placeholder="请输入"
type="number"
type="digit"
border="none"
:custom-style="inputStyle"
></uv-input>
@ -73,7 +73,7 @@ export default {
}
},
methods: {
handleWithdraw() {
async handleWithdraw() {
if (!this.realName) {
uni.showToast({
title: '请输入真实姓名',
@ -89,6 +89,26 @@ export default {
return
}
const subRes = await this.$api.promotion.withdraw({
name: this.realName,
money: this.amount
})
if (subRes.code === 200) {
uni.showToast({
title: '提现申请已提交',
icon: 'success'
})
uni.navigateBack({
delta: 1,
duration: 1000
})
} else {
uni.showToast({
title: subRes.msg,
icon: 'none'
})
}
//
console.log('提现信息:', {
realName: this.realName,


+ 27
- 4
subPages/user/discount.vue View File

@ -88,6 +88,7 @@ export default {
return {
mixinListApi: 'member.getCouponList',
currentTab: 0,
fromPage: '', //
tabList: [
{ name: '待使用' },
{ name: '已使用' },
@ -122,6 +123,13 @@ export default {
}
},
onLoad(options) {
//
if (options.from) {
this.fromPage = options.from
}
},
computed: {
},
@ -140,10 +148,25 @@ export default {
},
useCoupon(coupon) {
uni.showToast({
title: '跳转到使用页面',
icon: 'none'
})
//
if (this.fromPage === 'recharge') {
console.log('被选中了');
// 线
uni.$emit('couponSelected', {
id: coupon.id,
name: coupon.name,
money: coupon.money,
endTime: coupon.endTime
})
uni.navigateBack()
} else {
//
uni.showToast({
title: '跳转到使用页面',
icon: 'none'
})
}
}
}
}


+ 43
- 14
subPages/user/promote.vue View File

@ -32,18 +32,18 @@
<view class="main-container">
<!-- 个人信息容器 -->
<view class="profile-container">
<image class="profile-avatar" src="/static/待上传头像.png" mode="aspectFill"></image>
<image class="profile-avatar" :src="userInfo.avatar" mode="aspectFill"></image>
<view class="profile-info">
<text class="profile-name">战斗世界</text>
<text class="profile-id">ID: 5625354</text>
<text class="profile-name">{{ userInfo.name }}</text>
<!-- <text class="profile-id">ID: {{ userInfo.id }}</text> -->
</view>
<view class="profile-stats">
<view class="stat-item">
<text class="stat-number">888</text>
<text class="stat-number">{{ num }}</text>
<text class="stat-label">推广人数</text>
</view>
<view class="stat-item">
<text class="stat-number">341</text>
<text class="stat-number">{{ userInfo.price }}</text>
<text class="stat-label">总佣金</text>
</view>
</view>
@ -55,7 +55,7 @@
<image class="function-icon" src="/static/团队图标.png" mode="aspectFit"></image>
<text class="function-text">我的团队</text>
</view>
<view class="function-item">
<view class="function-item" @click="goQrcode">
<image class="function-icon" src="/static/二维码图标.png" mode="aspectFit"></image>
<text class="function-text">我的二维码</text>
</view>
@ -96,13 +96,14 @@
<!-- 底部固定按钮 -->
<view class="bottom-button-container">
<uv-button :custom-style="{
height: '82rpx',
borderRadius: '198rpx',
background: '#06DADC',
border: '2rpx solid #06DADC',
lineHeight: '82rpx',
fontSize: '36rpx'
}" type="primary">分享</uv-button>
}" type="primary" @click="goQrcode">分享</uv-button>
<uv-safe-bottom></uv-safe-bottom>
</view>
</view>
@ -115,6 +116,8 @@ export default {
data() {
return {
mixinListApi: 'promotion.water',
num: 0,
userInfo: {},
}
},
methods: {
@ -127,10 +130,11 @@ export default {
})
},
goCash() {
uni.navigateTo({
url: '/subPages/user/cash'
uni.requestMerchantTransfer({
})
},
getText(widhdraw) {
//
if (widhdraw.withdrawStatus !== '0') {
@ -140,9 +144,34 @@ export default {
}else if(widhdraw.status === '2'){
return '审核失败!'
}
}
}
},
// 广
async getStatistics() {
const res = await this.$api.promotion.statistics()
if (res.code === 200) {
this.num = res.result.num
}
},
//
async getUserInfo() {
const res = await this.$api.login.getUserInfo()
if (res.code === 200) {
this.userInfo = res.result
}
},
//
goQrcode() {
uni.navigateTo({
url: '/subPages/user/share'
})
},
},
async onShow() {
Promise.all([
this.getUserInfo(),
this.getStatistics()
])
},
}
</script>
@ -208,7 +237,7 @@ background: linear-gradient(180deg, #DEFFFF 0%, #FBFEFF 22.65%, #F0FBFF 100%);
// padding-top: 32rpx;
padding-right: 40rpx;
// padding-bottom: 32rpx;
padding-left: 40rpx;
// padding-left: 40rpx;
height: 200rpx;
.profile-avatar {
width: 128rpx;
@ -239,7 +268,7 @@ background: linear-gradient(180deg, #DEFFFF 0%, #FBFEFF 22.65%, #F0FBFF 100%);
.stat-item {
text-align: center;
margin-left: 60rpx;
margin-left: 100rpx;
.stat-number {
display: block;


+ 106
- 0
subPages/user/share.vue View File

@ -0,0 +1,106 @@
<template>
<view class="share-page">
<!-- <uv-status-bar></uv-status-bar> -->
<!-- 主要内容区域 -->
<view class="content">
<!-- 中间图片 -->
<view class="image-container">
<image
class="share-image"
:src="Qrcode"
mode="widthFix"
></image>
</view>
</view>
<!-- 底部固定按钮 -->
<view class="bottom-button-container">
<uv-button :custom-style="{
height: '82rpx',
borderRadius: '198rpx',
background: '#06DADC',
border: '2rpx solid #06DADC',
lineHeight: '82rpx',
fontSize: '36rpx'
}" type="primary" @click="save">保存到相册</uv-button>
<uv-safe-bottom></uv-safe-bottom>
</view>
</view>
</template>
<script>
export default {
data() {
return {
Qrcode: '',
}
},
methods: {
handleShare() {
//
uni.showToast({
title: '分享功能',
icon: 'none'
})
},
async getQrcode() {
// const res = await this.$api.promotion.qrCode()
// if (res.code === 200) {
uni.getImageInfo({
src: `${this.$config.baseURL}/promotion/qrCode?token=${uni.getStorageSync('token')}`,
success: function (image) {
console.log(image.width);
console.log(image.path);
this.Qrcode = image.path;
console.log(image.height);
}
});
// }
}
},
onShow() {
this.getQrcode()
},
}
</script>
<style lang="scss" scoped>
.share-page {
min-height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
.content {
flex: 1;
padding-bottom: 200rpx;
display: flex;
align-items: center;
justify-content: center;
.image-container {
display: flex;
justify-content: center;
align-items: center;
.share-image {
width: 670rpx;
border-radius: 16rpx;
}
}
}
//
.bottom-button-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 30rpx 40rpx;
background: rgba(255, 255, 255, 0.95);
border-top: 1px solid #F1F1F1;
}
</style>

+ 78
- 1
utils/common.js View File

@ -54,6 +54,8 @@ const checkPhone = (phone) => {
}
// 转换时间戳为yyyy-mm-dd
// params: 时间戳
// return yyyy-mmm-dd
const formatTime = (time) => {
if (!time) {
return '时间格式错误,需要传入时间戳'
@ -65,9 +67,84 @@ const formatTime = (time) => {
return `${year}-${month}-${day}`
}
// 计算yyyy-mm-dd与当前时间的差值
// params: yyyy-mm-dd格式的字符串
// return: 差值(天)
const calculateDateDifference = (dateString) => {
if (!dateString) {
return '时间格式错误,需要传入yyyy-mm-dd格式的字符串'
}
// 传入值为yyyy-mm-dd格式的字符串
const inputDate = new Date(dateString)
// 化为时间戳
// const inputTime = inputDate.getTime()
const currentDate = new Date()
const timeDifference = inputDate - currentDate
if (!(currentDate.setHours(0, 0, 0, 0) - inputDate.setHours(0, 0, 0, 0))) {
return 0
}
// 如果为负 返回-1
if (timeDifference < 0) {
return -1
}
// 计算天数
const dayDifference = Math.ceil(timeDifference / (1000 * 60 * 60 * 24))
return dayDifference
}
// 微信支付方法
// 传参1: 支付数据
// 传参2: 成功回调
// 传参3: 失败回调
// 传三个参数 支付数据 成功回调 失败回调
const wxPay = (paymentData, successCallback, failCallback) => {
uni.requestPayment({
provider: 'wxpay',
timeStamp: paymentData.timeStamp,
nonceStr: paymentData.nonceStr,
package: paymentData.packageValue,
signType: paymentData.signType,
paySign: paymentData.paySign,
success: (res) => {
if (successCallback) {
successCallback(res)
}
console.log('支付成功:', res)
uni.showToast({
title: '支付成功',
icon: 'success'
})
// 支付成功后返回上一页
setTimeout(() => {
this.goBack()
}, 1500)
},
fail: (err) => {
console.log('支付失败:', err)
if (failCallback) {
failCallback(err)
}
if (err.errMsg === 'requestPayment:fail cancel') {
uni.showToast({
title: '支付已取消',
icon: 'none'
})
} else {
uni.showToast({
title: '支付失败',
icon: 'none'
})
}
}
})
}
export {
authorize,
checkPhone,
formatTime
formatTime,
calculateDateDifference,
wxPay
}

+ 5
- 1
utils/index.js View File

@ -1,7 +1,9 @@
import {
authorize,
checkPhone,
formatTime
formatTime,
calculateDateDifference,
wxPay
} from '@/utils/common'
import {
@ -14,6 +16,8 @@ export default {
authorize,
checkPhone,
formatTime,
calculateDateDifference,
wxPay,
uploadImage,
chooseAndUpload,
getFileExtension


Loading…
Cancel
Save