Compare commits

4 Commits

28 changed files with 2024 additions and 1290 deletions

60
App.vue
View File

@@ -1,29 +1,55 @@
<script> <script>
// #ifdef APP-PLUS // #ifdef APP-PLUS
import update from "@/uni_modules/uni-upgrade-center-app/utils/check-update"; import update from "@/uni_modules/uni-upgrade-center-app/utils/check-update";
// #endif // #endif
export default { import { useUserStore } from '@/stores/user'
onLaunch: function() { export default {
console.log('App Launch') 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 // #ifdef APP-PLUS
update(); update();
// #endif // #endif
}, },
onShow: function() { onShow: function() {
console.log('App Show') console.log('App Show')
}, },
onHide: function() { onHide: function() {
console.log('App Hide') console.log('App Hide')
} },
} onTabItemTap: function() {
console.log('点击了');
}
}
</script> </script>
<style lang="scss"> <style lang="scss">
@import "@/style/tailwind.css"; @import "@/style/tailwind.css";
@import "@/style/ui.scss"; @import "@/style/ui.scss";
.container { .container {
padding: 15px; padding: 15px;
} }
</style> </style>

View File

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

View File

@@ -2,6 +2,9 @@
import { mainClient } from '@/api/clients/main' import { mainClient } from '@/api/clients/main'
import type { IApiResponse } from '@/api/types' import type { IApiResponse } from '@/api/types'
import type { IAgreement } from '@/types/user' import type { IAgreement } from '@/types/user'
import { useUserStore } from '@/stores/user'
export const commonApi = { export const commonApi = {
/** /**
@@ -36,8 +39,9 @@ export const commonApi = {
* @returns 消息列表 * @returns 消息列表
*/ */
getMessageList(isBook: number, isMedical: number, isSociology: number) { getMessageList(isBook: number, isMedical: number, isSociology: number) {
const userStore = useUserStore()
return mainClient.request<IMessageListResponse>({ return mainClient.request<IMessageListResponse>({
url: 'common/message/listByPage', url: userStore.token ? 'common/message/listByPage' : '/visitor/listByPage',
method: 'POST', method: 'POST',
data: { isBook, isMedical, isSociology } data: { isBook, isMedical, isSociology }
}) })

View File

@@ -14,6 +14,9 @@ import type {
} from '@/types/course' } from '@/types/course'
import type { ISearchRequest, ISearchResponse } from '@/types/search' import type { ISearchRequest, ISearchResponse } from '@/types/search'
import type { ICommentListResponse, IAddCommentResponse, IComment } from '@/types/comment' import type { ICommentListResponse, IAddCommentResponse, IComment } from '@/types/comment'
import { useUserStore } from '@/stores/user'
const client = createRequestClient({ baseURL: SERVICE_MAP.MAIN }) const client = createRequestClient({ baseURL: SERVICE_MAP.MAIN })
@@ -57,8 +60,9 @@ export const courseApi = {
page: number, page: number,
limit: number limit: number
}) { }) {
const userStore = useUserStore()
return client.request<IMarketCourseListResponse>({ return client.request<IMarketCourseListResponse>({
url: 'medical/home/getMarketCourseList', url: userStore.token ? 'medical/home/getMarketCourseList' : 'visitor/getMarketCourseList',
method: 'POST', method: 'POST',
data data
}) })

View File

@@ -7,6 +7,8 @@ import type {
IMarketCourseListResponse, IMarketCourseListResponse,
ICourseMedicalLabelsResponse ICourseMedicalLabelsResponse
} from '@/types/course' } from '@/types/course'
import { useUserStore } from '@/stores/user'
const client = createRequestClient({ baseURL: SERVICE_MAP.MAIN }) const client = createRequestClient({ baseURL: SERVICE_MAP.MAIN })
@@ -20,8 +22,9 @@ export const courseSubjectClassificationApi = {
* @returns 分类数据 * @returns 分类数据
*/ */
getCourseMedicalTree() { getCourseMedicalTree() {
const userStore = useUserStore()
return client.request<ICourseCategoryResponse>({ return client.request<ICourseCategoryResponse>({
url: 'medical/home/getCourseMedicalTree', url: userStore.token ? 'medical/home/getCourseMedicalTree' : '/visitor/getCourseMedicalTree',
method: 'POST', method: 'POST',
data: {} data: {}
}) })

View File

@@ -4,10 +4,10 @@ import { paymentClient } from '@/api/clients/payment'
import type { IApiResponse } from '@/api/types' import type { IApiResponse } from '@/api/types'
import type { import type {
IUserInfo, IUserInfo,
IVipInfo, IVipInfo,
IOrder, IOrder,
IVipPackage, IVipPackage,
ITransaction, ITransaction,
IFeedbackForm, IFeedbackForm,
IPageData IPageData
} from '@/types/user' } from '@/types/user'
@@ -159,7 +159,7 @@ export function uploadImage(filePath: string): Promise<string> {
url: `${SERVICE_MAP.MAIN}oss/fileoss`, url: `${SERVICE_MAP.MAIN}oss/fileoss`,
filePath, filePath,
name: 'file', name: 'file',
success: (res) => { success: (res: any) => {
try { try {
const data = JSON.parse(res.data) const data = JSON.parse(res.data)
if (data.url) { if (data.url) {
@@ -304,13 +304,57 @@ export async function getPointsData(current : number, limit : number, userId : s
* 迁移用户数据 * 迁移用户数据
* @param tel 旧账号 * @param tel 旧账号
* @param code 迁移验证码 * @param code 迁移验证码
* @param type 未迁移数据类型
* @return * @return
*/ */
export async function migrateUserData(data: { tel: string, code: string }) { export async function migrateUserData(data: { tel: string, code: string, type: string }) {
const res = await mainClient.request<IApiResponse>({ const res = await mainClient.request<IApiResponse>({
url: 'common/user/migrationWumenData', url: 'common/user/migrationWumenData',
method: 'POST', method: 'POST',
data data
}) })
return res return res
}
/**
* 获取用户迁移信息
* @return {
* alreadyMigration: 已迁移用户数
* notMigration: 未迁移用户数
* }
*/
export async function getUserMigrateInfo() {
const res = await mainClient.request<IApiResponse>({
url: 'common/user/getMigrationList',
method: 'POST',
})
return res
}
/**
* 我的湖分
* @return
*/
export async function getUserContributionData() {
const res = await mainClient.request<IApiResponse>({
url: 'common/userContribution/getUserContribution',
method: 'POST'
})
return res
}
/**
* 湖分列表
* @param current 当前页码
* @param limit 每页数量
* @param type 湖分类型
* @return
*/
export async function getUserContributionByTypeList(current : number, limit : number, type : string,) {
const res = await mainClient.request<IApiResponse>({
url: 'common/userContribution/getUserContributionByType',
method: 'POST',
data: { current, limit, type, }
})
return res
} }

View File

@@ -16,7 +16,7 @@
<!-- 商品列表 --> <!-- 商品列表 -->
<view class="selector-header"> <view class="selector-header">
<text class="title">{{ isFudu ? t('order.selectFuduScheme') : t('order.selectPurchaseScheme') }}</text> <text class="title">{{ t('order.goodsList') }}</text>
</view> </view>
<view class="goods-list"> <view class="goods-list">
<view <view
@@ -54,7 +54,6 @@ const { t } = useI18n()
interface Props { interface Props {
show: boolean show: boolean
goods: IGoods[] goods: IGoods[]
isFudu?: boolean // 是否为复读
} }
const props = defineProps<Props>() const props = defineProps<Props>()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"id": "uni-taimed-international-app", "id": "uni-wumen-international-app",
"name": "TaimedInternationalApp", "name": "wumen-international-app",
"displayName": "太湖国际", "displayName": "太湖国际",
"version": "1.0.3", "version": "1.0.3",
"description": "太湖国际", "description": "太湖国际",

View File

@@ -67,6 +67,24 @@
"navigationBarTitleText": "%user.feedback%", "navigationBarTitleText": "%user.feedback%",
"navigationStyle": "custom" "navigationStyle": "custom"
} }
},{
"path": "pages/user/certificate/index",
"style": {
"navigationBarTitleText": "%user.certificate%",
"navigationStyle": "custom"
}
},{
"path": "pages/user/hufen/index",
"style": {
"navigationBarTitleText": "%user.hufen%",
"navigationStyle": "custom"
}
},{
"path": "pages/user/hufen/forDetails",
"style": {
"navigationBarTitleText": "%user.hufen%",
"navigationStyle": "custom"
}
}, { }, {
"path": "pages/user/recharge/index", "path": "pages/user/recharge/index",
"style": { "style": {
@@ -219,6 +237,11 @@
"animationDuration": 200 "animationDuration": 200
} }
} }
}, {
"path": "pages/visitor/index",
"style": {
"navigationStyle": "custom"
}
} }
], ],
"tabBar": { "tabBar": {

View File

@@ -55,12 +55,8 @@
<wd-button size="small" type="warning" @click="handlePurchase"> <wd-button size="small" type="warning" @click="handlePurchase">
{{ $t('courseDetails.purchase') }} {{ $t('courseDetails.purchase') }}
</wd-button> </wd-button>
<wd-button <!-- 如果是复读显示复读按钮 -->
v-if="showRenewBtn" <wd-button v-if="canRenlearn" size="small" type="success" @click="handleRenlearn">
size="small"
type="success"
@click="handleRenew"
>
{{ $t('courseDetails.relearn') }} {{ $t('courseDetails.relearn') }}
</wd-button> </wd-button>
<wd-button size="small" type="primary" @click="goToVip"> <wd-button size="small" type="primary" @click="goToVip">
@@ -134,28 +130,42 @@
<text>暂无章节内容</text> <text>暂无章节内容</text>
</view> </view>
</view> </view>
<!-- 商品选择器 -->
<GoodsSelector
:show="showGoodsSelector"
:goods="goodsList"
@select="handleGoodsSelect"
@confirm="handleGoodsConfirm"
@close="closeGoodsSelector"
/>
<!-- 购买协议弹窗 -->
<Protocol :visible="showProtocol" @confirmPurchase="confirmPurchase" />
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { courseApi } from '@/api/modules/course' import { courseApi } from '@/api/modules/course'
import GoodsSelector from '@/components/order/GoodsSelector.vue'
import Protocol from './Protocol.vue'
import type { IChapter, ICatalogue, IVipInfo } from '@/types/course' import type { IChapter, ICatalogue, IVipInfo } from '@/types/course'
import type { IGoods } from '@/types/order'
interface Props { interface Props {
catalogues: ICatalogue[] catalogues: ICatalogue[]
userVip: IVipInfo | null userVip: IVipInfo | null
showRenewBtn?: boolean
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const emit = defineEmits<{ const emit = defineEmits<{
click: [chapter: IChapter], click: [chapter: IChapter],
purchase: [catalogue: ICatalogue], change: [index: number],
renew: [catalogue: ICatalogue], toVip: [],
toVip: [catalogue: ICatalogue], toDetail: [chapter: IChapter, catalogue: ICatalogue],
change: [index: number] loadPageData: [],
}>() }>()
// 当前目录索引 // 当前目录索引
@@ -167,14 +177,21 @@ const currentCatalogue = computed(() => {
// 当前目录的章节 // 当前目录的章节
const chapterList = ref<IChapter[]>([]) const chapterList = ref<IChapter[]>([])
// 显示续费按钮 // 当前目录是否是复读
const showRenewBtn = ref<boolean>(false) const isRelearn = ref<boolean>(false)
const canRenlearn = ref<boolean>(false)
// 判断目录是否已购买 // 判断目录是否已购买
const isPurchased = computed(() => { const isPurchased = computed(() => {
return currentCatalogue.value.isBuy === 1 return currentCatalogue.value.isBuy === 1
}) })
// 商品选择
const showGoodsSelector = ref(false)
const goodsList = ref<IGoods[]>([])
const selectedGoods = ref<IGoods | null>(null)
const showProtocol = ref(false)
/** /**
* 选择目录 * 选择目录
*/ */
@@ -201,9 +218,9 @@ const getChapters = async () => {
const checkRenewPayment = async () => { const checkRenewPayment = async () => {
if (currentCatalogue.value.isBuy === 0 && !props.userVip) { if (currentCatalogue.value.isBuy === 0 && !props.userVip) {
const renewRes = await courseApi.checkRenewPayment(currentCatalogue.value.id) const renewRes = await courseApi.checkRenewPayment(currentCatalogue.value.id)
showRenewBtn.value = renewRes.canRelearn || false canRenlearn.value = renewRes.canRelearn || false
} else { } else {
showRenewBtn.value = false canRenlearn.value = false
} }
} }
@@ -219,23 +236,98 @@ watch(() => props.catalogues, (newVal: ICatalogue[]) => {
}, { immediate: true, deep: true }) }, { immediate: true, deep: true })
// 购买 // 购买
const handlePurchase = () => { const handlePurchase = async () => {
emit('purchase', currentCatalogue.value) if (!currentCatalogue.value) return
isRelearn.value = false
const res = await courseApi.getProductListForCourse(currentCatalogue.value.id)
if (res.code === 0 && res.productList.length > 0) {
goodsList.value = res.productList
showGoodsSelector.value = true
} else {
uni.showToast({ title: '此课程暂无购买方式', icon: 'none' })
}
}
/**
* 选择商品
*/
const handleGoodsSelect = (goods: IGoods) => {
selectedGoods.value = goods
}
/**
* 确认购买
*/
const handleGoodsConfirm = () => {
showGoodsSelector.value = false
showProtocol.value = true
}
/**
* 关闭商品选择器
*/
const closeGoodsSelector = () => {
showGoodsSelector.value = false
}
/**
* 确认购买协议
*/
const confirmPurchase = () => {
showProtocol.value = false
if (!selectedGoods.value) return
showProtocol.value = false
if (isRelearn.value) {
uni.navigateTo({
url: `/pages/order/goodsConfirm?isRelearn=1`,
success: () => {
setTimeout(() => {
uni.$emit('selectedGoods', selectedGoods.value)
}, 100)
}
})
} else {
// 跳转到购买确认订单页
uni.navigateTo({
url: `/pages/order/goodsConfirm?goods=${selectedGoods.value.productId}`
})
}
} }
// 去开通vip // 去开通vip
const goToVip = () => { const goToVip = () => {
emit('toVip', currentCatalogue.value) emit('toVip')
} }
// 续费/复读 // 续费/复读
const handleRenew = () => { const handleRenlearn = async () => {
emit('renew', currentCatalogue.value) if (!currentCatalogue.value) return
isRelearn.value = true
const res = await courseApi.getRenewProductList(currentCatalogue.value.id)
if (res.productList.length > 0) {
goodsList.value = res.productList
showGoodsSelector.value = true
} else {
uni.showToast({ title: '暂无复读方案', icon: 'none' })
}
} }
// 领取免费课程 // 领取免费课程
const handleGetFreeCourse = async () => { const handleGetFreeCourse = async () => {
emit('getFreeCourse', currentCatalogue.value) if (!currentCatalogue.value) return
const res = await courseApi.startStudyForMF(currentCatalogue.value.id)
if (res.code === 0) {
uni.showToast({ title: '领取成功', icon: 'success' })
// 刷新页面数据
emit('loadPageData')
} else {
uni.showToast({ title: res.msg || '领取失败', icon: 'none' })
}
} }
/** /**
@@ -260,7 +352,7 @@ const canAccess = (chapter: IChapter): boolean => {
/** /**
* 点击章节 * 点击章节
*/ */
const handleChapterClick = (chapter: IChapter, catalogue: ICatalogue) => { const handleChapterClick = (chapter: IChapter) => {
if (!canAccess(chapter)) { if (!canAccess(chapter)) {
if (currentCatalogue.value.type === 0) { if (currentCatalogue.value.type === 0) {
uni.showToast({ uni.showToast({

View File

@@ -0,0 +1,91 @@
<template>
<wd-popup v-model="showProtocol" position="center">
<view class="protocol-popup">
<view class="protocol-title">温馨提示</view>
<view class="protocol-content">
<text>
用户您好本软件对于一个用户名及密码仅允许一部电子设备登陆多部设备使用同一用户名操作软件的行为属于违规操作发现违规一次将提出警告再次违规您的用户名将被封号无法正常登陆如因此对您使用带来不便敬请谅解
</text>
<text>
课程购买之后一年内不打开此一年内不会计算有效学习时间一年后会自动开始计算有效学习时间
</text>
<text>
本课程一经购买暂不支持退款敬请谅解
</text>
<view style="color: red; font-weight: bold"> : </view>
<view>
1.手机pad电脑均为可登陆电子设备均有唯一标识码一个用户名仅允许在一个手机或一个ipad或一个电脑登陆请根据您的使用习惯自行选择<br />
2.如若申请变更登陆设备请联系客服<br />
客服电话:021-08371305<br />
客服微信号:yilujiankangkefu<br />
3.如因违反上述使用规定...概不退款本公司保留追究用户相关法律责任的权利<br />
4.点击同意按钮即表示您同意遵守以上条款
</view>
</view>
<view class="protocol-actions">
<wd-button type="info" plain @click="showProtocol = false">不同意</wd-button>
<wd-button type="primary" @click="confirmPurchase">同意</wd-button>
</view>
</view>
</wd-popup>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
const props = defineProps<{
visible: boolean
}>()
console.log(props.visible)
const showProtocol = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val)
})
const emit = defineEmits<{
'update:visible': [boolean],
confirmPurchase: []
}>()
const confirmPurchase = () => {
emit('confirmPurchase')
}
</script>
<style lang="scss" scoped>
.protocol-popup {
width: 600rpx;
padding: 40rpx;
background-color: #fff;
border-radius: 12rpx;
.protocol-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
text-align: center;
margin-bottom: 30rpx;
}
.protocol-content {
max-height: 60vh;
overflow-y: auto;
font-size: 26rpx;
line-height: 1.8;
color: #666;
margin-bottom: 30rpx;
text {
display: block;
margin-bottom: 20rpx;
}
}
.protocol-actions {
display: flex;
gap: 20rpx;
}
}
</style>

View File

@@ -20,10 +20,8 @@
v-if="catalogueList.length > 0" v-if="catalogueList.length > 0"
:catalogues="catalogueList" :catalogues="catalogueList"
:userVip="userVip" :userVip="userVip"
@getFreeCourse="handleGetFreeCourse"
@purchase="handlePurchase"
@toVip="goToVip" @toVip="goToVip"
@renew="handleRenew" @loadPageData="loadPageData"
@toDetail="handleToDetail" @toDetail="handleToDetail"
/> />
@@ -89,55 +87,14 @@
/> />
</view> --> </view> -->
<!-- 商品选择器 -->
<GoodsSelector
:show="showGoodsSelector"
:goods="goodsList"
:isFudu="isFudu"
@select="handleGoodsSelect"
@confirm="handleGoodsConfirm"
@close="closeGoodsSelector"
/>
<!-- 购买协议弹窗 -->
<wd-popup v-model="showProtocol" position="center">
<view class="protocol-popup">
<view class="protocol-title">温馨提示</view>
<view class="protocol-content">
<text>
用户您好本软件对于一个用户名及密码仅允许一部电子设备登陆多部设备使用同一用户名操作软件的行为属于违规操作发现违规一次将提出警告再次违规您的用户名将被封号无法正常登陆如因此对您使用带来不便敬请谅解
</text>
<text>
课程购买之后一年内不打开此一年内不会计算有效学习时间一年后会自动开始计算有效学习时间
</text>
<text>
本课程一经购买暂不支持退款敬请谅解
</text>
<view style="color: red; font-weight: bold"> : </view>
<view>
1.手机pad电脑均为可登陆电子设备均有唯一标识码一个用户名仅允许在一个手机或一个ipad或一个电脑登陆请根据您的使用习惯自行选择<br />
2.如若申请变更登陆设备请联系客服<br />
客服电话:021-08371305<br />
客服微信号:yilujiankangkefu<br />
3.如因违反上述使用规定...概不退款本公司保留追究用户相关法律责任的权利<br />
4.点击同意按钮即表示您同意遵守以上条款
</view>
</view>
<view class="protocol-actions">
<wd-button type="info" plain @click="showProtocol = false">不同意</wd-button>
<wd-button type="primary" @click="confirmPurchase">同意</wd-button>
</view>
</view>
</wd-popup>
<!-- 评论编辑器 --> <!-- 评论编辑器 -->
<CommentEditor <!-- <CommentEditor
:show="showEditor" :show="showEditor"
:parentComment="replyComment" :parentComment="replyComment"
type="course" type="course"
@submit="handleCommentSubmit" @submit="handleCommentSubmit"
@close="closeCommentEditor" @close="closeCommentEditor"
/> /> -->
<!-- 返回顶部 --> <!-- 返回顶部 -->
<wd-backtop :scrollTop="scrollTop" custom-class="back-top"> <wd-backtop :scrollTop="scrollTop" custom-class="back-top">
@@ -153,7 +110,6 @@ import { useUserStore } from '@/stores/user'
import { courseApi } from '@/api/modules/course' import { courseApi } from '@/api/modules/course'
import CourseInfo from './components/CourseInfo.vue' import CourseInfo from './components/CourseInfo.vue'
import CatalogueList from './components/CatalogueList.vue' import CatalogueList from './components/CatalogueList.vue'
import GoodsSelector from '@/components/order/GoodsSelector.vue'
import CommentList from '@/components/comment/CommentList.vue' import CommentList from '@/components/comment/CommentList.vue'
import CommentEditor from '@/components/comment/CommentEditor.vue' import CommentEditor from '@/components/comment/CommentEditor.vue'
import type { ICourseDetail, ICatalogue, IChapter, IVipInfo } from '@/types/course' import type { ICourseDetail, ICatalogue, IChapter, IVipInfo } from '@/types/course'
@@ -172,14 +128,6 @@ const vipModuleList = ref<string[]>([])
const learningProgress = ref(0) const learningProgress = ref(0)
const relatedBooks = ref<IGoods[]>([]) const relatedBooks = ref<IGoods[]>([])
// 商品选择
const showGoodsSelector = ref(false)
const goodsList = ref<IGoods[]>([])
const selectedGoods = ref<IGoods | null>(null)
const showProtocol = ref(false)
const isFudu = ref(false)
const fuduCatalogueId = ref<number>(0)
// 评论相关 // 评论相关
const commentList = ref<IComment[]>([]) const commentList = ref<IComment[]>([])
const commentsLoading = ref(false) const commentsLoading = ref(false)
@@ -301,92 +249,6 @@ const goToVip = () => {
}) })
} }
/**
* 领取免费课程
*/
const handleGetFreeCourse = async (catalogue: ICatalogue) => {
if (!catalogue) return
const res = await courseApi.startStudyForMF(catalogue.id)
if (res.code === 0) {
uni.showToast({ title: '领取成功', icon: 'success' })
// 刷新页面数据
loadPageData()
} else {
uni.showToast({ title: res.msg || '领取失败', icon: 'none' })
}
}
/**
* 购买课程
*/
const handlePurchase = async (catalogue: ICatalogue) => {
if (!catalogue) return
isFudu.value = false
const res = await courseApi.getProductListForCourse(catalogue.id)
if (res.code === 0 && res.productList.length > 0) {
goodsList.value = res.productList
showGoodsSelector.value = true
} else {
uni.showToast({ title: '此课程暂无购买方式', icon: 'none' })
}
}
/**
* 续费/复读
*/
const handleRenew = async () => {
// if (!currentCatalogue.value) return
// isFudu.value = true
// fuduCatalogueId.value = currentCatalogue.value.id
// const res = await courseApi.getRenewProductList(currentCatalogue.value.id)
// if (res.code === 0 && res.productList.length > 0) {
// goodsList.value = res.productList
// showGoodsSelector.value = true
// } else {
// uni.showToast({ title: '暂无复读方案', icon: 'none' })
// }
}
/**
* 选择商品
*/
const handleGoodsSelect = (goods: IGoods) => {
selectedGoods.value = goods
}
/**
* 确认购买
*/
const handleGoodsConfirm = () => {
showGoodsSelector.value = false
showProtocol.value = true
}
/**
* 关闭商品选择器
*/
const closeGoodsSelector = () => {
showGoodsSelector.value = false
}
/**
* 确认购买协议
*/
const confirmPurchase = () => {
showProtocol.value = false
if (!selectedGoods.value) return
showProtocol.value = false
// 跳转到确认订单页
uni.navigateTo({
url: `/pages/order/goodsConfirm?goods=${selectedGoods.value.productId}`
})
}
/** /**
* 跳转到书籍详情 * 跳转到书籍详情
*/ */
@@ -740,40 +602,6 @@ onReachBottom(() => {
} }
} }
.protocol-popup {
width: 600rpx;
padding: 40rpx;
background-color: #fff;
border-radius: 12rpx;
.protocol-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
text-align: center;
margin-bottom: 30rpx;
}
.protocol-content {
max-height: 60vh;
overflow-y: auto;
font-size: 26rpx;
line-height: 1.8;
color: #666;
margin-bottom: 30rpx;
text {
display: block;
margin-bottom: 20rpx;
}
}
.protocol-actions {
display: flex;
gap: 20rpx;
}
}
:deep(.back-top) { :deep(.back-top) {
background-color: #fff !important; background-color: #fff !important;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);

View File

@@ -211,7 +211,9 @@ import { commonApi } from '@/api/modules/common'
import { getNotchHeight } from '@/utils/system' import { getNotchHeight } from '@/utils/system'
// import { onPageJump } from '@/utils' // import { onPageJump } from '@/utils'
import type { IMedicalTag, ICourse, INews } from '@/types/course' import type { IMedicalTag, ICourse, INews } from '@/types/course'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const { t } = useI18n() const { t } = useI18n()
// 系统信息 // 系统信息
@@ -239,18 +241,21 @@ const selectedFirstLevel = ref<number>('医学') // 当前选中的一级分类
* 学科点击处理 * 学科点击处理
*/ */
const handleFirstLevelClick = (item: string) => { const handleFirstLevelClick = (item: string) => {
selectedFirstLevel.value = item getPrompt()
switch (item) { if(!userStore.token) return
case '医学': selectedFirstLevel.value = item
getMedicalTags() switch (item) {
break case '医学':
case '心理学': getMedicalTags()
getSoulCateList() break
break case '心理学':
case '国学': getSoulCateList()
getSociologyCateList() break
break case '国学':
} getSociologyCateList()
break
}
} }
/** /**
* 医学 * 医学
@@ -326,15 +331,19 @@ const getSociologyCateList = async () => {
* 终极分类点击处理 * 终极分类点击处理
*/ */
const curseClickJump = (item: IMedicalTag) => { const curseClickJump = (item: IMedicalTag) => {
uni.navigateTo({ getPrompt()
url: `/pages/course/list/category?id=${item.id}&title=${item.title}&pid=${item.pid}&subject=${selectedFirstLevel.value}` if(!userStore.token) return
}) uni.navigateTo({
url: `/pages/course/list/category?id=${item.id}&title=${item.title}&pid=${item.pid}&subject=${selectedFirstLevel.value}`
})
} }
/** /**
* 页面跳转统一处理 * 页面跳转统一处理
*/ */
const onPageJump = (url: string, id?: number, title?: string) => { const onPageJump = (url: string, id?: number, title?: string) => {
getPrompt()
if(!userStore.token) return
let targetUrl = url let targetUrl = url
if (id !== undefined) { if (id !== undefined) {
targetUrl += `?id=${id}` targetUrl += `?id=${id}`
@@ -381,9 +390,12 @@ const getNewsList = async () => {
* 新闻点击处理 * 新闻点击处理
*/ */
const newsClick = (item: INews) => { const newsClick = (item: INews) => {
uni.navigateTo({ getPrompt()
url: `/pages/news/details?newsId=${item.id}&url=${item.url}&type=${item.type}` if(!userStore.token) return
}) uni.navigateTo({
url: `/pages/news/details?newsId=${item.id}&url=${item.url}&type=${item.type}`
})
} }
// 精彩试听 // 精彩试听
@@ -406,31 +418,59 @@ const getTryListenList = async () => {
} }
} }
/**
* 登录提示语
*/
const getPrompt = () => {
if(!userStore.token) {
uni.showModal({
title: '提示',
content: '请先登录后访问该页面',
confirmText: '去登录',
success: (res) => {
console.log(res, 'res');
if (res.confirm) uni.navigateTo({
url: '/pages/login/login'
});
}
});
}
}
/** /**
* 统一请求所有数据 * 统一请求所有数据
*/ */
const requestAll = async () => { const requestAll = async () => {
getLearnCourse() if(userStore.token){
getMedicalTags() getLearnCourse()
getTryListenList() }
getNewsList() getMedicalTags()
getTryListenList()
getNewsList()
} }
/** /**
* 页面挂载 * 页面挂载
*/ */
onMounted(() => { onMounted(() => {
if(!userStore.token) {
uni.navigateTo({
url: '/pages/login/login'
});
}
// 重置分类索引 // 重置分类索引
currentIndex.value = 0 currentIndex.value = 0
// 请求所有数据 // 请求所有数据
requestAll() requestAll()
console.log('进来了2');
}) })
/** /**
* 页面显示 * 页面显示
*/ */
onShow(() => { onShow(() => {
console.log('进来了1');
// 检查是否有固定的分类选择状态 // 检查是否有固定的分类选择状态
const fixed = uni.getStorageSync('fixed') const fixed = uni.getStorageSync('fixed')
if (fixed && currentItem.value) { if (fixed && currentItem.value) {

View File

@@ -115,11 +115,11 @@
</view> </view>
<!-- 游客体验 --> <!-- 游客体验 -->
<!-- <view class="youke-l"> <view class="youke-l">
<view @click="onPageJump('/pages/visitor/visitor')"> <view @click="onPageJump('/pages/course/index')">
{{ $t('login.noLogin') }} {{ $t('login.noLogin') }}
</view> </view>
</view> --> </view>
</view> </view>
<!-- 用户协议弹窗 --> <!-- 用户协议弹窗 -->
@@ -399,7 +399,7 @@ const yszc = () => {
* 页面跳转 * 页面跳转
*/ */
const onPageJump = (url: string) => { const onPageJump = (url: string) => {
uni.navigateTo({ uni.switchTab({
url: url, url: url,
}) })
} }

View File

@@ -91,22 +91,29 @@ const orderType = computed(() => {
* 页面加载 * 页面加载
*/ */
onLoad(async (options: any) => { onLoad(async (options: any) => {
if (options.goods) { try {
try { if (options.isRelearn == 1) {
uni.$on('selectedGoods', async (data: IOrderGoods) => {
// 获取用户信息
await getUserInfo()
// 处理商品数据
console.log('监听到传入的商品数据:', data)
goodsList.value = [ data ]
})
} else if (options.goods) {
// 获取用户信息 // 获取用户信息
await getUserInfo() await getUserInfo()
// 根据商品ID获取商品详细信息 // 根据商品ID获取商品详细信息
goodsIds.value = options.goods || '' goodsIds.value = options.goods || ''
isRelearn.value = options.isRelearn == '1'
getGoodsList() getGoodsList()
} catch (error) {
console.error('解析商品数据失败:', error)
uni.showToast({
title: '商品数据错误',
icon: 'none'
})
} }
} catch (error) {
console.error('解析商品数据失败:', error)
uni.showToast({
title: '商品数据错误',
icon: 'none'
})
} }
}) })
</script> </script>

View File

@@ -56,10 +56,10 @@ const orderType = ref<string>('')
/** /**
* 页面加载 * 页面加载
*/ */
onLoad(async () => { onLoad(() => {
try { try {
// 获取商品列表 // 获取商品列表
await uni.$on('selectedGoods', async (data: IOrderGoods) => { uni.$on('selectedGoods', async (data: IOrderGoods) => {
// 获取用户信息 // 获取用户信息
await getUserInfo() await getUserInfo()

View File

@@ -0,0 +1,134 @@
<template>
<view class="certificate-page">
<nav-bar :title="$t('user.certificate')"></nav-bar>
<view v-if="certificateList.length > 0">
<view style="margin: 10rpx;" >{{certificateList.length}}个证书</view>
<view class="certificate-list" v-for="(item,index) in certificateList" :key="index">
<view class="certificate-list-row">
<h3>证书编号{{item.bh}}</h3>
<text style="font-size: 26rpx; color: #999;">获得时间{{item.time}}</text>
</view>
<view class="certificate-certificate">
<view class="img" v-for="(i,index) in item.certificateUrl" :key="index">
<image @click="preveImg(i.url)" :src="i.url" mode="heightFix"></image>
</view>
<view class="certificate-detailed" @click="detailed(item)">详细信息</view>
</view>
</view>
</view>
<view v-else><wd-divider>您还未获得证书</wd-divider></view>
</view>
<wd-popup v-model="detailedState" position="bottom" :closeable="true">
<view class="detailed">
<view class="detailed-text">
证书详情
</view>
<view class="detailed-row">证书类型<text class="text">{{detailedData.a}}</text></view>
<view class="detailed-row">获得时间<text class="text">{{detailedData.time}}</text></view>
<view class="detailed-row">获得途径<text class="text">{{detailedData.b}}</text></view>
</view>
</wd-popup>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const detailedState = ref(false)
const detailedData = ref({})
// 模拟的 certificateList 数据
const certificateList = ref([
{ a: 'ZH', b: '吴门', bh: 1, time: '2025-6-20', certificateUrl: "https://ehh-private-01.oss-cn-beijing.aliyuncs.com/certificate/ca2140c3-d212-4d4e-9203-ddc161d50470.jpg,https://ehh-private-01.oss-cn-beijing.aliyuncs.com/certificate/18a7ea22-b75a-4ef6-9109-f448f45e424f.jpg" },
{ bh: 2, time: '2025-6-22', certificateUrl: "https://ehh-private-01.oss-cn-beijing.aliyuncs.com/certificate/ca2140c3-d212-4d4e-9203-ddc161d50470.jpg,https://ehh-private-01.oss-cn-beijing.aliyuncs.com/certificate/18a7ea22-b75a-4ef6-9109-f448f45e424f.jpg" }
]);
// 重新获取数据
certificateList.value = certificateList.value.map(item => {
return { ...item, certificateUrl: item.certificateUrl.split(',').map(url => ({ url })) };
});
/**
* 查看证书详情信息
*/
const detailed = (item) => {
detailedState.value = true
detailedData.value = item
}
/**
* 查看照片
*/
const preveImg = (url : any) => {
uni.previewImage({
urls: [url],
current: 0
});
}
</script>
<style lang="scss" scoped>
.certificate-page {
min-height: 100vh;
background-color: #f7faf9;
}
.certificate-list {
background: #fff;
border-radius: 15rpx;
overflow: hidden;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
margin: 20rpx;
padding: 20rpx;
}
.certificate-list-row {
display: flex;
justify-content: space-between;
}
.certificate-certificate {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 20rpx;
.img {
width: 36%;
overflow: hidden;
height: 300rpx;
image {
width: 100%;
height: 100%;
}
}
.certificate-detailed {
color: #55aaff;
border: #55aaff 1px solid;
padding: 10rpx 20rpx;
border-radius: 15rpx;
}
}
.detailed {
padding: 20rpx;
.detailed-text {
text-align: center;
margin-bottom: 40rpx;
}
.detailed-row {
color: #999;
margin: 20rpx 0;
font-size: 26rpx;
.text {
color: #000;
}
}
}
</style>

View File

@@ -0,0 +1,113 @@
<template>
<z-paging ref="paging" v-model="bookList" auto-show-back-to-top class="my-book-page" @query="hufenList"
:default-page-size="10">
<template #top>
<!-- 自定义导航栏 -->
<nav-bar :title="$t('user.hufenRecord')"></nav-bar>
</template>
<view class="recharge-record" v-if="(bookList && bookList.length > 0)">
<view class="go-gecharge">{{hufenData.nameValue}} {{$t('user.hufenRecord')}}</view>
<view class="recharge-record-block" v-for="(item, index) in bookList" :key="index">
<view class="recharge-record-block-row">{{item.createTime}}<text class="text">{{item.score}}</text>
</view>
<view class="time">{{item.detail}}</view>
</view>
</view>
</z-paging>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import { useUserStore } from '@/stores/user'
import { getUserContributionByTypeList } from '@/api/modules/user'
import { copyToClipboard } from '@/utils/index'
const { t } = useI18n()
const paging = ref<any>()
const userStore = useUserStore()
// 数据状态
const bookList = ref([])
const loading = ref(false)
const firstLoad = ref(true)
const hufenData = ref('')
// 湖分记录
async function hufenList(pageNo : number, pageSize : number) {
loading.value = true
try {
const res = await getUserContributionByTypeList(pageNo, pageSize, hufenData.value.type)
console.log(res, 'res');
paging.value.complete(res.list.records)
} catch (error) {
paging.value.complete(false)
console.error('Failed to load book list:', error)
} finally {
firstLoad.value = false
loading.value = false
}
}
onLoad((options) => {
hufenData.value = options
console.log(hufenData);
});
</script>
<style lang="scss" scoped>
.my-book-page {
background: #f7faf9;
min-height: 100vh;
.recharge-record {
background: #fff;
border-radius: 15rpx;
overflow: hidden;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
// padding: 20rpx;
margin: 20rpx;
.go-gecharge {
text-align: center;
background: linear-gradient(to right, #007bff, #17a2b8);
font-size: 30rpx;
font-weight: bold;
color: #fff;
padding: 20rpx;
margin-bottom: 20rpx;
}
.title {
font-size: 30rpx;
padding-left: 20rpx;
margin-bottom: 30rpx;
color: #007bff;
font-weight: bold;
}
.recharge-record-block {
border-bottom: 1px solid #e0e0e0;
padding: 20rpx;
.time {
font-size: 24rpx;
margin-bottom: 20rpx;
color: #343434
}
.recharge-record-block-row {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
// font-weight: 700;
color: #909090;
.text {
color: #007bff;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<view class="recharge-page">
<nav-bar :title="$t('user.iHufen')"></nav-bar>
<view class="menu-section" v-if="hufenList.list.length > 0">
<wd-cell-group border class="menu-list">
<wd-cell v-for="item in hufenList.list" :key="item.type" :title="item.dict_value" is-link
@click="handleMenuClick(item)">
<text class="menu-list-hufen">{{item.score}}</text><text class="menu-list-hufen-text">{{$t('user.hufen')}}</text>
</wd-cell>
</wd-cell-group>
</view>
<view v-else><wd-divider>您还未获得湖分</wd-divider></view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { getUserContributionData } from '@/api/modules/user'
const { t } = useI18n()
const hufenList = ref([])
/**
* 获取用户湖分
*/
const getHufen = async () => {
hufenList.value = await getUserContributionData()
console.log(hufenList.value.list)
}
const handleMenuClick = (item) => {
uni.navigateTo({
url: `/pages/user/hufen/forDetails?type=${item.type}&nameValue=${item.dict_value}`
})
}
onMounted(() => {
getHufen()
})
</script>
<style lang="scss" scoped>
.recharge-page {
min-height: 100vh;
background-color: #f7faf9;
}
.menu-section {
padding: 20rpx 20rpx;
}
.menu-list {
background: #fff;
border-radius: 15rpx;
overflow: hidden;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
.menu-list-hufen {
font-size: 36rpx;
color: #007bff;
margin-right: 6rpx;
}
.menu-list-hufen-text {
color: #007bff;
}
}
</style>

View File

@@ -1,5 +1,5 @@
<template> <template>
<view class="user-page" :style="{ paddingTop: getNotchHeight() + 30 + 'px' }"> <view class="user-page" :style="{ paddingTop: getNotchHeight() + 30 + 'px' }" v-if="userStore.token">
<!-- 设置图标 --> <!-- 设置图标 -->
<view class="settings-icon" :style="{ top: getNotchHeight() + 30 + 'px' }" @click="goSettings"> <view class="settings-icon" :style="{ top: getNotchHeight() + 30 + 'px' }" @click="goSettings">
<wd-icon name="setting1" size="24px" color="#666" /> <wd-icon name="setting1" size="24px" color="#666" />
@@ -23,18 +23,23 @@
<view class="vip-card-title">{{ $t('user.vip') }}</view> <view class="vip-card-title">{{ $t('user.vip') }}</view>
<view class="vip-card-content"> <view class="vip-card-content">
<view class="vip-item-list"> <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 v-else>办理课程VIP畅享更多权益</view>
</view> </view>
<wd-button v-if="vipInfo?.length > 0" plain type="primary" size="small" @click="goCourseVipSub">{{ $t('vip.renewal') }}</wd-button> <wd-button v-if="vipInfo?.length > 0" plain type="primary" size="small"
<wd-button v-else plain type="primary" size="small" @click="goCourseVipSub">{{ $t('vip.openVip') }}</wd-button> @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>
<view class="vip-card-content"> <view class="vip-card-content">
<view class="vip-item-list"> <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 v-else>办理电子书VIP畅享更多权益</view>
</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> </view>
</view> </view>
@@ -65,22 +70,28 @@
<!-- 功能菜单列表 --> <!-- 功能菜单列表 -->
<view class="menu-section"> <view class="menu-section">
<wd-cell-group border class="menu-list"> <wd-cell-group border class="menu-list">
<wd-cell v-for="item in menuItems" :key="item.id" :title="item.name" :label="item.desc" is-link @click="handleMenuClick(item)" /> <wd-cell v-for="item in menuItems" :key="item.id" :title="item.name" :label="item.desc" is-link
@click="handleMenuClick(item)">
<text v-if="item.hufenState" class="menu-list-hufen">{{hufenData.total ?? 0}}<text
style="margin-left: 6rpx;">湖分</text></text>
</wd-cell>
</wd-cell-group> </wd-cell-group>
</view> </view>
</view> </view>
<visitor v-else></visitor>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { useSysStore } from '@/stores/sys' import { useSysStore } from '@/stores/sys'
import { getUserInfo, getVipInfo } from '@/api/modules/user' import { getUserInfo, getVipInfo, getUserContributionData } from '@/api/modules/user'
import type { IVipInfo } from '@/types/user' import type { IVipInfo } from '@/types/user'
import { getNotchHeight } from '@/utils/system' import { getNotchHeight } from '@/utils/system'
import { parseTime } from '@/utils/index' import { parseTime } from '@/utils/index'
import { t } from '@/utils/i18n' import { t } from '@/utils/i18n'
import { onShow } from '@dcloudio/uni-app' import { onShow } from '@dcloudio/uni-app'
import visitor from '@/pages/visitor/index.vue';
const userStore = useUserStore() const userStore = useUserStore()
const sysStore = useSysStore() const sysStore = useSysStore()
@@ -140,7 +151,22 @@
// desc: t('user.migrateSubtitle'), // desc: t('user.migrateSubtitle'),
// type: 'pageJump' // 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('')
/** /**
* 获取平台信息 * 获取平台信息
@@ -161,6 +187,13 @@
} }
} }
/**
* 获取用户湖分
*/
const getHufen = async () => {
hufenData.value = await getUserContributionData()
}
/** /**
* 跳转到设置页面 * 跳转到设置页面
*/ */
@@ -243,11 +276,20 @@
} }
onShow(() => { onShow(() => {
getData() console.log(userInfo, 'userInfo');
if (userStore.token) {
getData()
}
}) })
onMounted(() => { onMounted(() => {
getPlatform() console.log(userInfo, 'userInfo');
if (userStore.token) {
getPlatform()
getHufen()
}
}) })
</script> </script>
@@ -255,7 +297,7 @@
$theme-color: #54a966; $theme-color: #54a966;
.user-page { .user-page {
min-height: 100vh; min-height: calc(100vh - 50px);
background-color: #f7faf9; background-color: #f7faf9;
} }
@@ -394,6 +436,16 @@
border-radius: 15rpx; border-radius: 15rpx;
overflow: hidden; overflow: hidden;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
.menu-list-hufen {
font-size: 36rpx;
color: #007bff;
text {
font-size: 26rpx;
}
}
} }
.chong_btn { .chong_btn {

View File

@@ -3,10 +3,20 @@
<!-- 自定义导航栏 --> <!-- 自定义导航栏 -->
<nav-bar :title="$t('user.dataMigrate')"></nav-bar> <nav-bar :title="$t('user.dataMigrate')"></nav-bar>
<view class="text-red-500 text-center mb-[20rpx]! font-bold">{{ $t('user.migrateWarning') }}</view> <view v-if="!!migrateInfo.notMigration" class="text-center mb-[20rpx]!">
<view v-if="!!migrateInfo.alreadyMigration">{{ $t('user.alreadyMigrated') }}{{ migrateInfo.alreadyMigration }}</view>
<view>{{ $t('user.notMigration') }}<text class="font-bold">{{ migrateInfo.notMigration }}</text></view>
</view>
<view v-else class="text-center mb-[20rpx]! bg-white p-[20rpx] rounded-[10rpx] shadow-[0_4rpx_12rpx_rgba(0,0,0,0.05)]">
<wd-text :text="$t('user.migratedCompleted')" type="warning" />
<wd-text :text="migrateInfo.alreadyMigration" type="warning" bold />
</view>
<view v-if="!!migrateInfo.notMigration" class="text-red-500 text-center mb-[20rpx]! font-bold">{{ $t('user.migrateWarning') }}</view>
<!-- 主要内容区域 --> <!-- 主要内容区域 -->
<wd-form ref="migrateForm" :model="formData" :rules="rules" :label-width="120" class="migrate-card p-[10rpx]"> <wd-form v-if="!!migrateInfo.notMigration" ref="migrateForm" :model="formData" :rules="rules" :label-width="120" class="migrate-card p-[10rpx]">
<wd-cell-group border> <wd-cell-group border>
<wd-input <wd-input
v-model="formData.tel" v-model="formData.tel"
@@ -60,22 +70,40 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue' import { ref, onMounted } from 'vue'
import { t } from '@/utils/i18n' import { t } from '@/utils/i18n'
import { migrateUserData } from '@/api/modules/user' import { migrateUserData, getUserMigrateInfo } from '@/api/modules/user'
import { useMessage } from '@/uni_modules/wot-design-uni' import { useMessage } from '@/uni_modules/wot-design-uni'
const message = useMessage() const message = useMessage()
const migrateInfo = ref({
alreadyMigration: '',
notMigration: ''
})
// 表单引用 // 表单引用
const migrateForm = ref() const migrateForm = ref()
// 表单数据 // 表单数据
const formData = ref({ const formData = ref({
tel: '', tel: '',
code: '' code: '',
type: ''
}) })
// 获取用户迁移信息
const getMigrateInfo = async () => {
const res = await getUserMigrateInfo()
migrateInfo.value.alreadyMigration = res.alreadyMigration
// migrateInfo.value.notMigration = res.notMigration
}
onMounted(() => {
getMigrateInfo()
})
// 表单验证规则 // 表单验证规则
const rules = ref({ const rules = ref({
tel: [ tel: [
@@ -113,6 +141,7 @@ const handleSubmit = async () => {
} }
// 处理迁移 // 处理迁移
const submitMigrate = async () => { const submitMigrate = async () => {
formData.value.type = migrateInfo.value.notMigration
await migrateUserData(formData.value) await migrateUserData(formData.value)
uni.showToast({ uni.showToast({
title: t('user.migrateSuccess'), title: t('user.migrateSuccess'),

138
pages/visitor/index.vue Normal file
View File

@@ -0,0 +1,138 @@
<template>
<view class="visitor">
<view class="visitor-block">
<view style="display: flex;">
<image class="visitor_img" src="/static/logo.png" mode="aspectFil">
</image>
<text class="visitor-text" @click="gologin">立即登录</text>
</view>
<wd-cell-group border class="visitor-list">
<wd-cell v-for="item in menuItems" :title="item.name" is-link @click="handleMenuClick(item)">
</wd-cell>
</wd-cell-group>
</view>
</view>
<wd-action-sheet v-model="isShareSheetOpen" title="选择分享渠道" :panels="panels" @select="handleShare" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
const menuItems = ref([
{
name: '分享APP'
},
{
name: '关于我们',
}
])
const isShareSheetOpen = ref(false)
const panels = ref([
{
iconUrl: '/static/contact-person.png',
title: '微信消息'
},
{
iconUrl: '/static/moments.png',
title: '朋友圈'
}
])
// 打开分享菜单
const openShareSheet = () => {
isShareSheetOpen.value = true
}
// 选择分享渠道后执行分享逻辑
const handleShare = (action) => {
console.log(action, 'action');
isShareSheetOpen.value = false // 关闭菜单
if (action.index == 0) {
// 分享到好友
uni.share({
provider: "weixin",
scene: "WXSceneSession",
type: 0,
href: '',
title: "吴门医述",
summary: "我正在使用吴门医述提升自己,赶紧跟我一起来体验吧!",
imageUrl: "static/icon/home_icon_logo.png",
success: function (res) {
console.log("success:" + JSON.stringify(res));
},
fail: function (err) {
console.log("fail:" + JSON.stringify(err));
},
});
} else if (action.index == 1) {
// 分享到朋友圈
uni.share({
provider: "weixin",
scene: "WXSceneTimeline",
type: 0,
href: '',
title: "吴门医述",
summary: "我正在使用吴门医述提升自己,赶紧跟我一起来体验吧!",
imageUrl: "static/icon/home_icon_logo.png",
success: function (res) {
console.log("success:" + JSON.stringify(res));
},
fail: function (err) {
console.log("fail:" + JSON.stringify(err));
},
});
}
}
const handleMenuClick = (item : { name : string }) => {
if (item.name === '关于我们') {
uni.navigateTo({
url: '/pages/user/about/index'
})
} else {
isShareSheetOpen.value = true
}
}
const gologin = () => {
uni.navigateTo({
url: '/pages/login/login'
})
}
</script>
<style lang="scss" scoped>
.visitor {
background: #f4f7ff;
min-height: 100vh;
}
.visitor-block {
padding: 40rpx 20rpx;
.visitor_img {
width: 150rpx;
height: 150rpx;
background-color: #fff;
border-radius: 60px;
}
.visitor-text {
margin-top: 30rpx;
margin-left: 20rpx;
font-weight: bold;
font-size: 36rpx;
}
}
.visitor-list {
background: #fff;
border-radius: 15rpx;
overflow: hidden;
margin-top: 40rpx;
font-weight: bold;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
</style>

BIN
static/contact-person.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/moments.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -8,6 +8,7 @@
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace; "Courier New", monospace;
--color-red-500: oklch(63.7% 0.237 25.331); --color-red-500: oklch(63.7% 0.237 25.331);
--color-white: #fff;
--spacing: 0.25rem; --spacing: 0.25rem;
--text-xs: 0.75rem; --text-xs: 0.75rem;
--text-xs--line-height: calc(1 / 0.75); --text-xs--line-height: calc(1 / 0.75);
@@ -265,16 +266,25 @@
.flex-wrap { .flex-wrap {
flex-wrap: wrap; flex-wrap: wrap;
} }
.rounded-\[10rpx\] {
border-radius: 10rpx;
}
.border { .border {
border-style: var(--tw-border-style); border-style: var(--tw-border-style);
border-width: 1px; border-width: 1px;
} }
.bg-white {
background-color: var(--color-white);
}
.p-0\! { .p-0\! {
padding: calc(var(--spacing) * 0) !important; padding: calc(var(--spacing) * 0) !important;
} }
.p-\[10rpx\] { .p-\[10rpx\] {
padding: 10rpx; padding: 10rpx;
} }
.p-\[20rpx\] {
padding: 20rpx;
}
.p-\[30rpx\] { .p-\[30rpx\] {
padding: 30rpx; padding: 30rpx;
} }
@@ -337,6 +347,10 @@
--tw-ordinal: ordinal; --tw-ordinal: ordinal;
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,); font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
} }
.shadow-\[0_4rpx_12rpx_rgba\(0\,0\,0\,0\.05\)\] {
--tw-shadow: 0 4rpx 12rpx 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 { .ring {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); --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); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);

View File

@@ -116,3 +116,15 @@ export interface IPageData<T> {
pages: number pages: number
[key: string]: any [key: string]: any
} }
/**
* 订单接口
*/
export interface IOrder {
id: number
orderType: string // 订单类型
changeAmount: number
remark: string
createTime: string
[key: string]: any
}