2026-03-12 18:57:28 +08:00

951 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- 头部导航栏 -->
<view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
title="肝胆课件"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eeeeee"
>
<template v-slot:right>
<view class="collect-img" @click="goSearch" style="margin-right: 20rpx;">
<image class="img-icon" :src="searchImg" mode="aspectFill" />
</view>
</template>
</uni-nav-bar>
</view>
<view class="courseware-container">
<!-- 排序和筛选栏 -->
<view class="filter-bar">
<view class="filter-item" @click="toggleSort">
<text class="filter-text active">{{sort==0?'最新':'最热'}}</text>
<view class="newbox">
<up-image :src="upImg" width="20rpx" height="26rpx" ></up-image>
</view>
</view>
<view class="divider"></view>
<view class="filter-item" @click="showFilterPopup">
<text class="filter-text" :class="{active:isFilterActive}">筛选</text>
<view class="filterbox">
<up-image :src="isFilterActive ? filterOn : filter" width="30rpx" height="30rpx" ></up-image>
</view>
</view>
</view>
<!-- 课件列表 -->
<scroll-view class="courseware-list" scroll-y="true"
refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
:show-scrollbar="false">
<view
class="courseware-item"
v-for="(item, index) in coursewareList"
:key="index"
@click="viewCourseware(item)"
>
<view class="item-left">
<view class="pdf-icon">
<up-image :src="item.type=='pdf'?pptImg:wordImg" width="114rpx" height="114rpx" ></up-image>
</view>
</view>
<view class="item-content">
<view class="item-title twoline">{{item.title}}</view>
<view class="info">
<view class="author-name">{{ item.providername }}</view>
<view class="item-author">
<view>{{ item.hospitalname }}</view>
</view>
</view>
<view class="item-stats">
<view class="views">
<uni-icons type="eye" size="30rpx" color="#999"></uni-icons>
<text class="view-count">{{ item.readnum }}人阅读</text>
</view>
<view class="price" v-if="item.price>=0">
<view class="priceImg" style="margin-top: -4rpx;">
<up-image :src="downLoadImg" width="32rpx" height="32rpx" ></up-image>
</view>
<text class="price-value" v-if="item.price>0 && item.discount>0"><text class="money-unit">¥</text>{{fromatPrice(item.price/100*item.discount)}}</text>
<text class="yuanjia" v-if="item.price>0 && item.discount<1 && item.discount>0">原价<text class="jiaprice">{{fromatPrice(item.price/100)}}</text></text>
<text v-else-if="item.price<=0 || item.discount===0 || item.price*item.discount==0 " class="free">免费</text>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 筛选弹窗 -->
<view class="filter-popup" v-if="showFilter" @click="hideFilterPopup">
<view class="filter-content" @click.stop>
<!-- 筛选标签网格 -->
<view class="filter-tags">
<view
class="tag-item"
v-for="(tag, index) in filterTags"
:key="index"
:class="{ active: tag.selected }"
@click="toggleTag(index)"
>
{{ tag.NAME}}
</view>
</view>
<!-- 底部按钮 -->
<view class="filter-buttons">
<view class="btn-reset" @click="resetFilter">重置</view>
<view class="btn-confirm" @click="confirmFilter">确定</view>
</view>
</view>
</view>
<!-- 投稿按钮 -->
<view class="submit-btn" @click="submitCourseware">
<up-image :src="getPptImg" width="152rpx" height="80rpx" ></up-image>
</view>
</view>
<unidialog
:visible="pptVisible"
:cancelText="'取消'"
:confirmText="'保存二维码'"
@close="pptVisible=false"
@confirm="pptConfirm"
>
<template v-slot:content>
<view class="ppt-content">
<image :src="contactImg" width="80rpx" height="80rpx" class="contact-img"></image>
<view class="ppt-content-title">
<text style="color:#666">APP下载课件为pdf格式扫码联系平台文献助手获取原版PPT</text>
</view>
</view>
</template>
</unidialog>
</template>
<script setup>
import { ref } from 'vue';
import unidialog from "@/components/dialog/dialog.vue";
import { onShow,onLoad,onBackPress} from "@dcloudio/uni-app";
import searchImg from "@/static/search.png";
import filter from "@/static/cb_screen_no.png"
import filterOn from "@/static/cb_screen_yes.png"
import upImg from "@/static/cb_up.png"
import downImg from "@/static/cb_up.png"
import tougaoImg from "@/static/kejiantougao.png"
import getPptImg from "@/static/getPpt.png"
import downLoadImg from "@/static/wdxz.png"
import pptImg from "@/static/pdf.png"
import wordImg from "@/static/word.png"
import contactImg from "@/static/contact.png"
import api from '@/api/api.js';
import navTo from '@/utils/navTo.js';
const isFilterActive=ref(false)
const pptVisible=ref(false)
const pptContent=ref('肝胆相照将稍后与您沟通课件分享,谢谢您对平台的支持!')
const hcp_token=ref('')
const keywords=ref('')
const sort = ref(0); // 默认最新排序
// 响应式数据
const refreshing = ref(false);
const loading = ref(false);
const noMore = ref(false);
const page = ref(1);
const pageSize = ref(10);
const pptConfirm=()=>{
saveContactImgToAlbum();
pptVisible.value=false;
//feedBack();
}
const saveContactImgToAlbum = () => {
uni.getImageInfo({
src: contactImg,
success: (imgRes) => {
uni.saveImageToPhotosAlbum({
filePath: imgRes.path,
success: () => {
uni.showToast({
title: '图片已保存到相册',
icon: 'none'
});
},
fail: (err) => {
const errMsg = (err && err.errMsg) || '';
if (errMsg.includes('auth deny') || errMsg.includes('authorize no response') || errMsg.includes('denied')) {
uni.showModal({
title: '提示',
content: '请先在系统设置中允许访问相册',
showCancel: true,
confirmText: '去设置',
success: (res) => {
if (res.confirm) {
uni.openSetting();
}
}
});
return;
}
uni.showToast({
title: '保存失败,请稍后重试',
icon: 'none'
});
}
});
},
fail: () => {
uni.showToast({
title: '图片读取失败',
icon: 'none'
});
}
});
}
// 响应式数据
const coursewareList = ref([]);
onBackPress(()=>{
plus.runtime.quit();
return true;
})
const checkUser= async()=>{
const res = await api.checkUser();
if(res.code==200){
hcp_token.value=res.data.hcp_token
console.log(hcp_token.value)
}
}
onLoad(()=>{
checkUser();
loadData(false);
})
const goSearch = () => {
uni.sendNativeEvent(
"goHomeSearch",
{
msg: "ppt",
},
(ret) => {
console.log(ret);
}
);
};
// 方法
const goBack = () => {
uni.navigateBack({
fail(){
uni.redirectTo({
url: '/pages/index/index'
});
}
});
};
const showSearch = () => {
uni.showToast({
title: '搜索功能',
icon: 'none'
});
};
const toggleSort = () => {
sort.value = sort.value === 0 ? 1 : 0;
console.log('切换排序:', sort.value);
page.value=1;
coursewareList.value = [];
noMore.value = false;
// 切换排序后重新加载数据
loadData(false);
};
const viewCourseware = (item) => {
// let wv=plus.webview.create(item.preview_path,'',{})
// let newstr=`document.cookie="hcp_token=${hcp_token.value};domain=.igandan.com;path=/;"`
// wv.onloaded = e=>{
// console.log(newstr)
// wv.evalJS(newstr);
// wv.evalJS(`document.cookie="hcp_from=expert_app;domain=.igandan.com;path=/"`)
// wv.show()
// }
navTo({
url:`/pages_app/pptDetail/pptDetail?src=${encodeURIComponent(item.preview_path)}&uuid=${item.uuid}`
})
};
const submitCourseware = () => {
pptVisible.value=true;
};
const feedBack=()=>{
const expertUuid=uni.getStorageSync('userInfo').uuid
api.feedBack({
content: '我要共享课件',
expertUuid:expertUuid
}).then((res)=>{
uni.showToast({
title: '谢谢您的支持',
icon: 'none'
});
})
}
const loadGuideTags = async () => {
try {
const res = await api.guideTag({
type:6
});
console.log('指南标签API响应:', res);
if(res && res.code === 200 && res.data) {
// 将API返回的标签数据转换为筛选标签格式
filterTags.value = res.data.map(tag => ({
...tag,
selected: false
}));
console.log('指南标签加载成功:', filterTags.value);
}
} catch (e) {
console.error('加载指南标签失败:', e);
}
};
// 下拉刷新
const onRefresh = async () => {
refreshing.value = true;
try {
// 重置数据
page.value = 1;
noMore.value = false;
coursewareList.value = [];
// 重新加载第一页数据
await loadData(false);
uni.showToast({
title: '刷新成功',
icon: 'none'
});
} catch (error) {
uni.showToast({
title: '刷新失败',
icon: 'error'
});
} finally {
refreshing.value = false;
}
};
// 上拉加载更多
const onLoadMore = async () => {
console.log('=== onLoadMore 触发 ===');
console.log('当前状态:', {
loading: loading.value,
noMore: noMore.value,
page: page.value,
listLength: coursewareList.value.length
});
if (loading.value || noMore.value) {
console.log('条件不满足,跳过加载:', {
loading: loading.value,
noMore: noMore.value
});
return;
}
console.log('条件满足,开始加载更多数据');
await loadData(false);
};
// 加载数据
const loadData = async (isRefresh = false) => {
if (isRefresh) {
page.value = 1;
coursewareList.value = [];
noMore.value = false;
}
if (loading.value || noMore.value) return;
loading.value = true;
try {
console.log(`开始加载第${page.value}页课件数据`);
// 调用API获取课件列表
const res = await api.ganDanFileByKeyWords({
page: page.value,
sort: sort.value,
// 可以根据需要添加其他筛选参数
keywords:keywords.value,
title: '',
// fileType: selectedFileType.value,
// sortBy: sortType.value
});
console.log('课件列表API响应:', res);
if (res && res.code === 200) {
let newItems = [];
let totalCount = 0;
// 解析API返回的数据结构
if (res.data && res.data.list) {
newItems = res.data.list;
totalCount = res.data.totalRow;
console.log('使用 res.data.list 结构');
} else if (res.data && Array.isArray(res.data)) {
newItems = res.data;
totalCount = res.total || 0;
console.log('使用 res.data 数组结构');
} else {
console.error('无法识别的数据结构:', res.data);
noMore.value = true;
return;
}
console.log('解析后的数据:', { newItems, totalCount, newItemsLength: newItems.length });
if (Array.isArray(newItems)) {
if (isRefresh) {
coursewareList.value = newItems;
console.log('刷新模式:替换列表数据');
} else {
coursewareList.value.push(...newItems);
console.log('加载更多:追加数据到列表');
}
// 判断是否还有更多数据
if (newItems.length < pageSize.value || coursewareList.value.length >= totalCount) {
noMore.value = true;
console.log('没有更多数据了');
} else {
// 只有在加载更多模式下才递增页码
if (!isRefresh) {
page.value=page.value+1;
console.log(`还有更多数据,下一页: ${page.value}`);
}
}
} else {
console.error('API返回的数据不是数组格式:', newItems);
noMore.value = true;
}
} else {
console.error('加载课件列表失败:', res.message);
uni.showToast({
title: res.message || '加载失败',
icon: 'error'
});
}
} catch (error) {
console.error('加载课件列表异常:', error);
uni.showToast({
title: '网络异常,请重试',
icon: 'error'
});
} finally {
loading.value = false;
}
};
const showFilter = ref(false)
// 筛选弹窗相关数据
const filterTags = ref([
{ name: '指南解读', selected: false },
{ name: '病例分析', selected: false },
{ name: '学术讲座', selected: false },
{ name: '手术视频', selected: false },
{ name: '研究进展', selected: false },
{ name: '肝病治疗', selected: false },
{ name: '肝癌诊断', selected: false },
{ name: '肝移植', selected: false },
{ name: '微创手术', selected: false },
{ name: '免疫治疗', selected: false },
{ name: '靶向治疗', selected: false },
{ name: '化疗方案', selected: false }
]);
// 筛选弹窗相关方法
const showFilterPopup = () => {
showFilter.value = true;
};
const hideFilterPopup = () => {
showFilter.value = false;
};
const toggleTag = (index) => {
// 已选中的标签数量
const selectedCount = filterTags.value.filter(tag => tag.selected).length;
// 当前是未选中 -> 试图选中,且已达到上限
if (!filterTags.value[index].selected && selectedCount >= 3) {
uni.showToast({
title: '最多选择3个标签',
icon: 'none'
});
return;
}
filterTags.value[index].selected = !filterTags.value[index].selected;
//isFilterActive.value = filterTags.value.some(tag => tag.selected);
};
const resetFilter = () => {
filterTags.value.forEach(tag => tag.selected = false);
//isFilterActive.value = false;
};
const confirmFilter = () => {
const selectedTags = filterTags.value.filter(tag => tag.selected);
console.log('选中的筛选标签:', selectedTags);
let words='';
for (var i = 0; i < selectedTags.length; i++) {
if(words){
words+=","+selectedTags[i].NAME
}else{
words=selectedTags[i].NAME
}
}
keywords.value=words;
isFilterActive.value =true;
hideFilterPopup();
page.value=1;
coursewareList.value = [];
noMore.value = false;
loadData(false);
// 这里可以根据选中的标签进行数据筛选
};
const fromatPrice=(price)=>{
if(price<1){
return price.toFixed(2)
}else{
return price.toFixed(1)
}
}
onShow(() => {
// 页面显示时加载数据
console.log('页面显示,开始加载课件数据');
loadGuideTags()
});
// 测试加载更多功能
const testLoadMore = () => {
console.log('=== 手动测试加载更多 ===');
console.log('当前状态:', {
loading: loading.value,
noMore: noMore.value,
page: page.value,
listLength: coursewareList.value.length
});
if (!loading.value && !noMore.value) {
onLoadMore();
} else {
uni.showToast({
title: `加载中: ${loading.value}, 无更多: ${noMore.value}`,
icon: 'none',
duration: 2000
});
}
};
</script>
<style lang="scss" scoped>
// 变量定义
$primary-color: #e74c3c;
$text-primary: #333333;
$text-secondary: #666666;
$text-light: #999999;
$border-color: #e5e5e5;
$background-color: #f8f9fa;
$white: #ffffff;
$submit-btn-color: #20b2aa;
$theme-color: #8B2316;
$white: #fff;
.contact-img{
width: 280rpx;
height: 280rpx;
margin: 0 auto;
display: block;
}
// 混合器
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.img-icon{
width: 34rpx;
height: 34rpx;
}
@mixin flex-between {
display: flex;
align-items: center;
justify-content: space-between;
}
@mixin shadow {
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.courseware-container {
height: calc(100vh - 180rpx);
background-color: $background-color;
}
.status-bar {
height: 44rpx;
background-color: $white;
@include flex-between;
padding: 0 30rpx;
font-size: 24rpx;
color: $text-primary;
.status-left {
.time {
font-weight: 500;
}
}
.status-right {
display: flex;
align-items: center;
gap: 20rpx;
.network-info,
.signal,
.wifi,
.battery {
font-size: 22rpx;
}
}
}
.header {
height: 88rpx;
background-color: $white;
@include flex-between;
padding: 0 30rpx;
border-bottom: 1rpx solid $border-color;
position: sticky;
top: 0;
z-index: 100;
}
.filter-bar {
height: 100rpx;
background-color: $white;
@include flex-center;
border-bottom: 1rpx solid $border-color;
position: sticky;
top: calc(var(--status-bar-height) + 44px);
z-index: 99;
.divider{
width: 2rpx;
height: 30rpx;
margin:0 80rpx;
background-color: #999;
}
.filter-item{
display:flex;
align-items: center;
.filterbox{
margin-top: 0rpx;
}
.newbox{
margin-top: -11rpx;
}
.filter-text{
color:#666;
}
.filter-text.active{
color:#8B2316
}
}
}
.courseware-list {
position: fixed;
top:calc(var(--status-bar-height) + 44px + 100rpx);
flex: 1;
bottom:0;
left:0rpx;
right:0rpx;
padding-top: 20rpx;
.courseware-item {
margin:0 30rpx 20rpx;
background-color: $white;
border-radius: 16rpx;
padding: 30rpx 20rpx;
@include shadow;
display: flex;
.item-left {
.pdf-icon {
font-size: 60rpx;
color: $primary-color;
}
}
.item-content {
width:500rpx;
display: flex;
margin-left: 10px;
flex-direction: column;
margin-right: 30rpx;
.item-title{
font-size: 32rpx;
color:#333;
}
.info{
display: flex;
font-size: 26rpx;
color:#666;
width:100%;
min-width: 0;
margin:10rpx 0;
overflow: hidden;
}
.author-name{
white-space: nowrap;
}
.item-author {
flex:1;
min-width: 0;
max-width: 100%;
margin-left: 10rpx;
margin-right: 30rpx;
overflow: hidden;
view{
width:100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.item-stats {
display: flex;
align-items: center;
.views {
display: flex;
align-items: center;
gap: 8rpx;
.eye-icon {
font-size: 24rpx;
color: $text-light;
}
.view-count {
font-size: 26rpx;
color: $text-light;
}
}
.download {
.download-icon {
font-size: 32rpx;
color: $text-light;
}
}
.price {
display: flex;
margin-left: 30rpx;
align-items: center;
gap: 4rpx;
.price-symbol {
font-size: 24rpx;
color: $primary-color;
font-weight: 500;
}
.free{
font-size: 26rpx;
color:#999;
}
.yuanjia{
font-size: 26rpx;
margin-left: 10rpx;
color:#999;
.jiaprice{
text-decoration: line-through;
}
}
.priceImg{
margin-top: 4rpx;
}
.price-value {
font-size: 26rpx;
color: $primary-color;
.money-unit{
font-size: 28rpx;
}
}
}
}
}
}
}
.submit-btn {
position: fixed;
right: 0rpx;
bottom: 100rpx;
width: 152rpx;
height: 80rpx;
z-index: 100;
.submit-icon {
font-size: 32rpx;
}
.submit-text {
font-size: 28rpx;
color: $white;
font-weight: 500;
}
}
// 筛选弹窗样式
.filter-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
// background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
z-index: 9999;
.filter-content {
margin-top:calc(var(--status-bar-height) + 44px + 102rpx);
background-color: $white;
// border-radius: 20rpx 20rpx 0 0;
padding: 20rpx 30rpx 60rpx;
width: 100%;
max-height: 80vh;
.filter-tags {
display: flex;
flex-wrap: wrap;
margin-bottom: 60rpx;
max-height: 65vh;
overflow-y: auto;
.tag-item {
background-color: #f8f8f8;
color: #666;
padding: 6rpx 0rpx;
width:150rpx;
margin:8rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
border-radius: 30rpx;
font-size: 26rpx;
border: 2rpx solid #efefef;
transition: all 0.3s ease;
&.active {
background-color: #fff;
color: $theme-color;
border-color: $theme-color;
}
}
}
.filter-buttons {
display: flex;
gap: 20rpx;
position: fixed;
bottom: 30rpx;
left: 30rpx;
right: 30rpx;
.btn-reset,
.btn-confirm {
flex: 1;
height: 70rpx;
border-radius: 14rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 500;
}
.btn-reset {
background-color: $white;
color: $theme-color;
border: 2rpx solid $theme-color;
}
.btn-confirm {
border: 2rpx solid $theme-color;
background-color: $theme-color;
color: $white;
}
}
}
}
// 加载状态样式
.loading-state,
.empty-state,
.load-more-state,
.no-more-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 20rpx;
color: $text-secondary;
font-size: 28rpx;
text {
margin-top: 20rpx;
}
}
.loading-state {
color: $primary-color;
}
.empty-state {
color: $text-secondary;
}
.load-more-state {
padding: 40rpx 20rpx;
color: $text-secondary;
}
.no-more-state {
padding: 40rpx 20rpx;
color: #999;
}
</style>