3.11提交

This commit is contained in:
zoujiandong 2026-03-11 13:13:10 +08:00
parent 40f5ddf4a5
commit 08e3ba22c5
22 changed files with 2845 additions and 185 deletions

View File

@ -713,7 +713,14 @@ const api = {
}, },
getGanDanFileOrder(data){ getGanDanFileOrder(data){
return request('/expertPay/getGanDanFileOrder', data, 'post', false); return request('/expertPay/getGanDanFileOrder', data, 'post', false);
} },
publishOutPatient(data){
return request('/expertAPI/addGroupSendMsg4YunXin', data, 'post', false);
},
polularScienceArticleListIndexNew(data){
return request('/expertAPI/polularScienceArticleListIndexNew', data, 'post', false);
},
//https://dev-app.igandan.com/app/expertPay/getGanDanFileOrder //https://dev-app.igandan.com/app/expertPay/getGanDanFileOrder
} }

View File

@ -514,6 +514,17 @@
} }
} }
}, },
// {
// "path": "search/search",
// "style": {
// "navigationStyle": "custom",
// "navigationBarRightButton":{ "hide": true},
// "navigationBarTitleText": "uni-app分页",
// "app": {
// "bounce": "none"
// }
// }
// },
{ {
"path": "myAnswer/myAnswer", "path": "myAnswer/myAnswer",
"style": { "style": {
@ -1075,17 +1086,7 @@
// } // }
// } // }
// }, // },
// {
// "path": "search/search",
// "style": {
// "navigationStyle": "custom",
// "navigationBarRightButton":{ "hide": true},
// "navigationBarTitleText": "uni-app分页",
// "app": {
// "bounce": "none"
// }
// }
// },
// { // {
// "path": "video/video", // "path": "video/video",
// "style": { // "style": {
@ -1568,6 +1569,16 @@
"bounce": "none" "bounce": "none"
} }
} }
},
{
"path": "video/searchVideo",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app分页",
"app": {
"bounce": "none"
}
}
} }
] ]
} }
@ -1698,7 +1709,7 @@
"list": [ "list": [
{ {
"name": "", "name": "",
"path": "pages_chat/outPatient/outPatient", "path": "pages_app/patientMsg/patientMsg",
"query": "" "query": ""
} }
] ]

View File

@ -84,7 +84,7 @@
<view class="patient-list" v-if="openGroups[gi]"> <view class="patient-list" v-if="openGroups[gi]">
<view class="patient-item" v-for="(patient, index) in getVisiblePatients(group, gi)" :key="patient.uuid || index" @click="goPatientDetail(patient.uuid)"> <view class="patient-item" v-for="(patient, index) in getVisiblePatients(group, gi)" :key="patient.uuid || index" @click="goPatientDetail(patient.uuid)">
<view class="patient-avatar"> <view class="patient-avatar">
<up-image :src="patient._photoUrl" width="80rpx" height="80rpx" mode="aspectFill"></up-image> <up-image :src="patient._avatarLoadError ? defaultImg : patient._photoUrl" width="80rpx" height="80rpx" mode="aspectFill" @error="handlePatientAvatarError(patient)"></up-image>
</view> </view>
<view class="patient-info"> <view class="patient-info">
<view class="patient-name">{{ patient._displayName }}</view> <view class="patient-name">{{ patient._displayName }}</view>
@ -110,6 +110,7 @@ import api from '@/api/api.js';
import docUrl from '@/utils/docUrl' import docUrl from '@/utils/docUrl'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import navTo from '@/utils/navTo.js' import navTo from '@/utils/navTo.js'
import defaultImg from "@/static/default.png"
const list_sort = ref(0); const list_sort = ref(0);
const group_sort = ref(0); const group_sort = ref(0);
const groupSortSelected = ref(false); const groupSortSelected = ref(false);
@ -196,7 +197,7 @@ const preprocessGroups = (rawGroups) => {
list.forEach((patient) => { list.forEach((patient) => {
patient._formattedJoinDate = formatYMD(patient.join_date); patient._formattedJoinDate = formatYMD(patient.join_date);
patient._displayName = patient.realName || patient.nickname || '-'; patient._displayName = patient.realName || patient.nickname || '-';
patient._photoUrl = `${docUrl}${patient.photo || ''}`; patient._photoUrl = patient.photo ? `${docUrl}${patient.photo}` : defaultImg;
}); });
return { return {
...group, ...group,
@ -205,6 +206,11 @@ const preprocessGroups = (rawGroups) => {
}); });
}; };
const handlePatientAvatarError = (patient) => {
if (!patient) return;
patient._avatarLoadError = true;
};
const startProgressiveRender = (idx) => { const startProgressiveRender = (idx) => {
const group = groups.value[idx]; const group = groups.value[idx];
if (!group) return; if (!group) return;

File diff suppressed because it is too large Load Diff

View File

@ -95,10 +95,73 @@
</scroll-view> </scroll-view>
<!-- 患者列表区域 --> <!-- 患者列表区域 -->
<view class="patient-list" v-if="activeTab === 'list'"> <view class="patient-list" v-show="activeTab === 'list'">
<view class="listbox" style="z-index:99;" v-if="showAlert">
<image :src="alertImg" class="alert-img" mode="widthFix"></image>
<image :src="delImg" class="del-img" mode="widthFix" @click="hideAlert"></image>
</view>
<view class="listbox" v-show="patientList.length>0">
<!-- 使用 up-index-list 索引组件数据动态渲染 -->
<up-index-list :index-list="indexList" v-show="loadFinish" >
<view class="listbox"> <!-- 特殊操作项 -->
<template #header>
<view class="special-actions" >
<view class="action-item" @click="addNewPatient">
<view class="action-icon new-patient">
<uni-icons type="person" size="24" color="#ffffff"></uni-icons>
<uni-icons type="plus" size="16" color="#ffffff" style="position: absolute; right: 8rpx; bottom: 8rpx;"></uni-icons>
</view>
<text class="action-text">新的患者<text class="new-patient-count" v-if="applyList.length > 0">(待审核{{ applyList.length }})</text></text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
<view class="action-item" @click="managePatientGroups">
<view class="action-icon group-icon">
<view class="grid-icon">
<view class="grid-item"></view>
<view class="grid-item"></view>
<view class="grid-item"></view>
<view class="grid-item"></view>
</view>
</view>
<text class="action-text">患者分组 <text class="new-patient-count" v-if="patientList.length > 0">(随访{{ patientList.length }})</text></text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
</template>
<template v-for="group in patientGroups" :key="group.letter" >
<up-index-item >
<up-index-anchor :text="group.letter" />
<view class="group-section">
<view class="patient-item" v-for="item in group.items" :key="item.uuid || item.id" >
<!-- <template v-if="item.placeholder">
<view class="patient-avatar-placeholder">
<uni-icons type="person" size="32" color="#ffffff" @click="goPatientDetail(item.uuid)"></uni-icons>
</view>
</template> -->
<image class="patient-avatar" :src="item._avatarLoadError ? defaultImg : item.avatarUrl" mode="aspectFill" @error="handlePatientAvatarError(item)" @click="goPatientDetail(item.uuid)"></image>
<view class="patient-info" @click="goPatientDetail(item.uuid)">
<text class="patient-name">{{ item.realName }}</text>
<view class="patient-badge" v-if="item.badge">
<text class="badge-text">{{ item.badge }}</text>
</view>
</view>
<view class="patient-status">
<uni-icons type="compose" size="20" color="#8B2316" @click.stop="editPatient(item.uuid)"></uni-icons>
<text class="follow-date">随访于{{ item.joinDateYMD }}</text>
</view>
</view>
</view>
</up-index-item>
</template>
</up-index-list>
</view>
<view class="listbox" v-show="patientList.length === 0">
<view class="special-actions" > <view class="special-actions" >
<view class="action-item" @click="addNewPatient"> <view class="action-item" @click="addNewPatient">
<view class="action-icon new-patient"> <view class="action-icon new-patient">
@ -122,42 +185,10 @@
<uni-icons type="right" size="20" color="#999"></uni-icons> <uni-icons type="right" size="20" color="#999"></uni-icons>
</view> </view>
</view> </view>
<!-- 使用 up-index-list 索引组件数据动态渲染 --> <empty ></empty>
<up-index-list :index-list="indexList" v-show="loadFinish" v-if="patientList.length > 0">
<template v-for="group in patientGroups" :key="group.letter" >
<up-index-item >
<up-index-anchor :text="group.letter" v-if="group.items.length > 0"/>
<view class="group-section">
<view class="patient-item" v-for="item in group.items" :key="item.uuid || item.id" v-if="group.items.length > 0">
<template v-if="item.placeholder">
<view class="patient-avatar-placeholder">
<uni-icons type="person" size="32" color="#ffffff" @click="goPatientDetail(item.uuid)"></uni-icons>
</view>
</template>
<template v-else>
<image class="patient-avatar" :src="item.avatarUrl" mode="aspectFill" @click="goPatientDetail(item.uuid)"></image>
</template>
<view class="patient-info" @click="goPatientDetail(item.uuid)">
<text class="patient-name">{{ item.realName }}</text>
<view class="patient-badge" v-if="item.badge">
<text class="badge-text">{{ item.badge }}</text>
</view>
</view>
<view class="patient-status">
<up-image :src="editImg" width="35rpx" height="35rpx" @click.stop="editPatient(item.uuid)"></up-image>
<text class="follow-date">随访于{{ item.joinDateYMD }}</text>
</view>
</view>
<empty v-if="group.items.length === 0" :emptyDesc="'您暂无随访患者'"></empty>
</view>
</up-index-item>
</template>
</up-index-list>
<empty v-if="patientList.length === 0"></empty>
</view> </view>
</view> </view>
<view class="plan" v-if="activeTab === 'plan'"> <view class="plan" v-show="activeTab === 'plan'">
<scroll-view <scroll-view
class="plan-scroll" class="plan-scroll"
scroll-y="true" scroll-y="true"
@ -249,19 +280,21 @@
import { onShow,onLoad,onBackPress} from "@dcloudio/uni-app"; import { onShow,onLoad,onBackPress} from "@dcloudio/uni-app";
import dayImg from "@/static/visit_data11.png" import dayImg from "@/static/visit_data11.png"
import planImg from "@/static/visitplan.png" import planImg from "@/static/visitplan.png"
import editImg from "@/static/edit_patitent.png"
import api from '@/api/api.js'; import api from '@/api/api.js';
import navTo from '@/utils/navTo.js'; import navTo from '@/utils/navTo.js';
import docUrl from '@/utils/docUrl.js'; import docUrl from '@/utils/docUrl.js';
import defaultImg from "@/static/default.png"
const patientList = ref([]); const patientList = ref([]);
const patientDataLoaded = ref(false); const patientDataLoaded = ref(false);
const patientDataLoading = ref(false); const patientDataLoading = ref(false);
const firstLetterCache = new Map(); const firstLetterCache = new Map();
const showAlert = ref(false);
const navHeight = ref(40); const navHeight = ref(40);
onBackPress(() => { onBackPress(() => {
plus.runtime.quit(); plus.runtime.quit();
return true; return true;
}); });
import alertImg from "@/static/patientgif.png"
const getNavHeight = () => { const getNavHeight = () => {
const systemInfo = uni.getSystemInfoSync(); const systemInfo = uni.getSystemInfoSync();
console.log(2223); console.log(2223);
@ -272,7 +305,6 @@
import pinyin from 'pinyin'; import pinyin from 'pinyin';
import dayjs from 'dayjs' import dayjs from 'dayjs'
import lineImg from "@/static/item_visitplan_fg.png" import lineImg from "@/static/item_visitplan_fg.png"
import empty from '@/components/empty/empty.vue'
const loadFinish = ref(false); const loadFinish = ref(false);
//import ConversationList from './conversation-list/index.vue' //import ConversationList from './conversation-list/index.vue'
const title = ref('患者消息'); const title = ref('患者消息');
@ -287,6 +319,10 @@
url: `/pages_app/patientSetting/patientSetting?uuid=${uuid}` url: `/pages_app/patientSetting/patientSetting?uuid=${uuid}`
}) })
} }
const handlePatientAvatarError = (item) => {
if (!item) return
item._avatarLoadError = true
}
const goCode = () => { const goCode = () => {
navTo({ navTo({
url: `/pages_app/myCode/myCode` url: `/pages_app/myCode/myCode`
@ -387,7 +423,7 @@
const p = source[i] || {}; const p = source[i] || {};
const normalized = { const normalized = {
...p, ...p,
avatarUrl: p.photo ? `${docUrl}${p.photo}` : '', avatarUrl: p.photo ? `${docUrl}${p.photo}` : defaultImg,
joinDateYMD: formatYMD(p.join_date) joinDateYMD: formatYMD(p.join_date)
}; };
const first = getFirstLetter(normalized.realName || ''); const first = getFirstLetter(normalized.realName || '');
@ -397,10 +433,8 @@
} }
const letters = Array.from(map.keys()).sort((a,b) => a.localeCompare(b)); const letters = Array.from(map.keys()).sort((a,b) => a.localeCompare(b));
patientGroups.value = letters.map(l => ({ letter: l, items: map.get(l) })); patientGroups.value = letters.map(l => ({ letter: l, items: map.get(l) }));
console.log(patientGroups.value)
rebuildIndexList(); rebuildIndexList();
if(source.length === 0){ if(patientList.value.length==0){
console.log(111122)
indexList.value=[] indexList.value=[]
} }
nextTick(() => { nextTick(() => {
@ -485,6 +519,10 @@
}); });
} }
}; };
const hideAlert = () => {
showAlert.value = false;
uni.setStorageSync('hasAlert', true);
}
const patientListByGBK = async () => { const patientListByGBK = async () => {
if (patientDataLoading.value) return; if (patientDataLoading.value) return;
patientDataLoading.value = true; patientDataLoading.value = true;
@ -495,6 +533,15 @@
patientList.value = res.data || []; patientList.value = res.data || [];
buildGroupsFromPatients(); buildGroupsFromPatients();
patientDataLoaded.value = true; patientDataLoaded.value = true;
if(res.data.length > 1000){
const hasAlert = uni.getStorageSync('hasAlert');
if(!hasAlert){
showAlert.value = true;
}else{
showAlert.value = false;
}
//showAlert.value = true;
}
} else { } else {
uni.hideLoading(); uni.hideLoading();
} }
@ -619,15 +666,14 @@
break; break;
case 'list': case 'list':
title.value = '患者列表'; title.value = '患者列表';
// if (patientDataLoaded.value) { if (patientDataLoaded.value) {
// loadFinish.value = true; loadFinish.value = true;
// break; break;
// } }
uni.showLoading({ uni.showLoading({
title: '加载中...', title: '加载中...',
mask: true mask: true
}); });
getApplyList();
patientListByGBK(); patientListByGBK();
// //
break; break;
@ -883,7 +929,7 @@
} }
.tab-text { .tab-text {
font-size: 32rpx; font-size: 28rpx;
color: #999999; color: #999999;
transition: color 0.3s; transition: color 0.3s;
} }
@ -915,13 +961,12 @@
// //
.patient-list { .patient-list {
:deep(.u-index-list__letter){
top:50%!important;
}
:deep(.u-index-list__scroll-view){ :deep(.u-index-list__scroll-view){
height:calc(100vh - var(--status-bar-height) - 44px - 103rpx); height:calc(100vh - var(--status-bar-height) - 44px - 103rpx);
} }
:deep(.hide-index-bar .u-index-list__sidebar),
:deep(.hide-index-bar .u-index-list__index-box) {
display: none !important;
}
// position: fixed; // position: fixed;
width: 100%; width: 100%;

View File

@ -31,13 +31,19 @@
<!-- 列表 --> <!-- 列表 -->
<scroll-view class="list" scroll-y> <scroll-view class="list" scroll-y>
<view class="item" @click="toggle(p.uuid)" v-for="p in availablePatientList" :key="p.uuid"> <view class="item" @click="toggle(p.uuid)" v-for="p in availablePatientList" :key="p.uuid">
<image class="avatar" :src="docUrl + (p.photo || '')" mode="aspectFill" /> <image class="avatar" :src="getAvatarSrc(p)" mode="aspectFill" @error="handleAvatarError(p)" />
<view class="name">{{ p.nickname || p.realName }}</view> <view class="name">{{ p.realName || p.nickname}}</view>
<view class="check" > <view class="check" >
<view class="circle" :class="{ active: selectedIds.includes(p.uuid) }"></view> <view class="circle" :class="{ active: selectedIds.includes(p.uuid) }"></view>
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
<view class="bottom-actions">
<view class="select-all-btn" @click="toggleSelectAll">
{{ isAllSelected ? '全不选' : '全选' }}
</view>
</view>
</view> </view>
</template> </template>
@ -47,12 +53,17 @@
import { onShow,onLoad} from "@dcloudio/uni-app"; import { onShow,onLoad} from "@dcloudio/uni-app";
import api from '@/api/api.js' import api from '@/api/api.js'
import navTo from '@/utils/navTo.js' import navTo from '@/utils/navTo.js'
import defaultImg from "@/static/default.png"
const from = ref(''); const from = ref('');
const keyword = ref('') const keyword = ref('')
const selectedIds = ref([]) const selectedIds = ref([])
const patientList = ref([]) const patientList = ref([])
const selectedDetail = ref([]) const selectedDetail = ref([])
const availablePatientList = ref([]) const availablePatientList = ref([])
const isAllSelected = computed(() => {
const total = patientList.value.length
return total > 0 && selectedIds.value.length === total
})
// //
// const availablePatientList = computed(() => { // const availablePatientList = computed(() => {
// return patientList.value // return patientList.value
@ -127,6 +138,32 @@
} }
} }
const getAvatarSrc = (patient) => {
if (patient?._avatarLoadError) return defaultImg
return patient?.photo ? (docUrl + patient.photo) : defaultImg
}
const handleAvatarError = (patient) => {
if (!patient) return
patient._avatarLoadError = true
}
const toggleSelectAll = () => {
if (!patientList.value.length) return
if (isAllSelected.value) {
selectedIds.value = []
selectedDetail.value = []
return
}
selectedIds.value = patientList.value.map(item => item.uuid)
selectedDetail.value = patientList.value.map(item => ({
uuid: item.uuid,
realName: item?.realName || '',
photo: item?.photo || '',
nickName: item?.nickName || ''
}))
}
const onSearch = () => { const onSearch = () => {
if(keyword.value.replace(/\s/g, "")){ if(keyword.value.replace(/\s/g, "")){
availablePatientList.value = patientList.value.filter(p => p.realName.indexOf(keyword.value) !== -1 || (p.nickname && p.nickname.indexOf(keyword.value) !== -1) || p.mobile.indexOf(keyword.value) !== -1) availablePatientList.value = patientList.value.filter(p => p.realName.indexOf(keyword.value) !== -1 || (p.nickname && p.nickname.indexOf(keyword.value) !== -1) || p.mobile.indexOf(keyword.value) !== -1)
@ -137,6 +174,15 @@
} }
const goBack = () => uni.navigateBack() const goBack = () => uni.navigateBack()
const confirmSelect = () => { const confirmSelect = () => {
if(from.value == 'chatMsg'){
if(selectedIds.value.length >200){
uni.showToast({
title: '选择人数不能超过200',
icon: 'none'
})
return;
}
}
const payload = { ids: selectedIds.value, list: selectedDetail.value } const payload = { ids: selectedIds.value, list: selectedDetail.value }
// //
try { try {
@ -185,7 +231,14 @@
width: 88rpx; height: 72rpx; background:#fff; width: 88rpx; height: 72rpx; background:#fff;
} }
} }
.list{ border-radius: 12rpx; } .list{
border-radius: 12rpx;
position: fixed;
left: 0;
right: 0;
top: calc(var(--status-bar-height) + 64px + 94rpx);
bottom: 130rpx;
}
.item{background:#fff; display:flex; align-items:center;padding: 24rpx 30rpx; border-bottom: 2rpx solid #eee; } .item{background:#fff; display:flex; align-items:center;padding: 24rpx 30rpx; border-bottom: 2rpx solid #eee; }
.avatar{ width: 96rpx; height:96rpx; border-radius: 16rpx; background:#ffe; } .avatar{ width: 96rpx; height:96rpx; border-radius: 16rpx; background:#ffe; }
.name{ flex:1; margin-left: 20rpx; font-size: 32rpx; color:#333; } .name{ flex:1; margin-left: 20rpx; font-size: 32rpx; color:#333; }
@ -193,4 +246,24 @@
.circle{ width: 40rpx; height: 40rpx; border-radius: 50%; border: 2rpx solid #cfcfcf; } .circle{ width: 40rpx; height: 40rpx; border-radius: 50%; border: 2rpx solid #cfcfcf; }
.circle.active{ background:#8B2316; border-color:#8B2316; position: relative; } .circle.active{ background:#8B2316; border-color:#8B2316; position: relative; }
.circle.active::after{ content:''; position:absolute; left: 14rpx; top: 6rpx; width: 10rpx; height: 18rpx; border: 4rpx solid #fff; border-top: 0; border-left: 0; transform: rotate(45deg); } .circle.active::after{ content:''; position:absolute; left: 14rpx; top: 6rpx; width: 10rpx; height: 18rpx; border: 4rpx solid #fff; border-top: 0; border-left: 0; transform: rotate(45deg); }
.bottom-actions{
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #fff;
border-top: 1rpx solid #f0f0f0;
padding: 20rpx 30rpx 20rpx;
}
.select-all-btn{
height: 88rpx;
border-radius: 12rpx;
color: #8B2316;
background: #fff;
border: 2rpx solid #8B2316;
font-size: 30rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style> </style>

View File

@ -31,8 +31,8 @@
<!-- 列表 --> <!-- 列表 -->
<scroll-view class="list" scroll-y> <scroll-view class="list" scroll-y>
<view class="item" @click="toggle(p.uuid)" v-for="p in availablePatientList" :key="p.uuid"> <view class="item" @click="toggle(p.uuid)" v-for="p in availablePatientList" :key="p.uuid">
<image class="avatar" :src="docUrl + (p.photo || '')" mode="aspectFill" /> <image class="avatar" :src="getAvatarSrc(p)" mode="aspectFill" @error="handleAvatarError(p)" />
<view class="name">{{ p.nickname || p.realName }}</view> <view class="name">{{ p.realName || p.nickname }}</view>
<view class="check" > <view class="check" >
<view class="circle" :class="{ active: selectedIds.includes(p.uuid) }"></view> <view class="circle" :class="{ active: selectedIds.includes(p.uuid) }"></view>
</view> </view>
@ -46,7 +46,7 @@
import docUrl from '@/utils/docUrl.js' import docUrl from '@/utils/docUrl.js'
import { onShow,onLoad} from "@dcloudio/uni-app"; import { onShow,onLoad} from "@dcloudio/uni-app";
import api from '@/api/api.js' import api from '@/api/api.js'
import defaultImg from "@/static/default.png"
const keyword = ref('') const keyword = ref('')
const selectedIds = ref([]) const selectedIds = ref([])
const patientList = ref([]) const patientList = ref([])
@ -129,6 +129,16 @@
} }
} }
const getAvatarSrc = (patient) => {
if (patient?._avatarLoadError) return defaultImg
return patient?.photo ? (docUrl + patient.photo) : defaultImg
}
const handleAvatarError = (patient) => {
if (!patient) return
patient._avatarLoadError = true
}
const onSearch = () => { const onSearch = () => {
if(keyword.value.replace(/\s/g, "")){ if(keyword.value.replace(/\s/g, "")){
availablePatientList.value = patientList.value.filter(p => p.realName.indexOf(keyword.value) !== -1 || (p.nickname && p.nickname.indexOf(keyword.value) !== -1) || p.mobile.indexOf(keyword.value) !== -1) availablePatientList.value = patientList.value.filter(p => p.realName.indexOf(keyword.value) !== -1 || (p.nickname && p.nickname.indexOf(keyword.value) !== -1) || p.mobile.indexOf(keyword.value) !== -1)

View File

@ -99,6 +99,17 @@
<!-- 底部导航栏 --> <!-- 底部导航栏 -->
</view> </view>
<unidialog
:visible="noticeVisible"
:title="'是否确定发送此患教科普'"
:content="content"
:confirmText="'发送'"
:cancelText="'取消'"
@close="noticeVisible=false"
@confirm="noticeConfirm"
>
</unidialog>
</template> </template>
<script setup> <script setup>
@ -111,7 +122,16 @@
import api from "@/api/api.js" import api from "@/api/api.js"
import docUrl from '@/utils/docUrl.js'; import docUrl from '@/utils/docUrl.js';
import navTo from '@/utils/navTo.js'; import navTo from '@/utils/navTo.js';
import unidialog from '@/components/dialog/dialog.vue'
import navBar from '@/components/navBar/navBar.vue'; import navBar from '@/components/navBar/navBar.vue';
const content=ref('');
const noticeVisible=ref(false);
const noticeConfirm=()=>{
noticeVisible.value=false;
content.value='';
uni.$emit('articelItem', articeItem.value);
uni.navigateBack();
}
const type=ref(1) const type=ref(1)
@ -125,6 +145,7 @@
const activeTab = ref(0); const activeTab = ref(0);
const showFilter = ref(false); const showFilter = ref(false);
const isFilterActive = ref(false); const isFilterActive = ref(false);
const articeItem=ref(null);
const toggleSort = () => { const toggleSort = () => {
type.value = type.value === 1 ? 2 : 1; type.value = type.value === 1 ? 2 : 1;
// //
@ -250,9 +271,11 @@
const goToDetail = (article) => { const goToDetail = (article) => {
console.log('查看文章详情:', article); console.log('查看文章详情:', article);
articeItem.value=article;
noticeVisible.value=true;
content.value=article.title;
// //
uni.$emit('articelItem', article);
uni.navigateBack();
}; };
// //
@ -365,7 +388,7 @@ $padding-small: 10px;
.top{ .top{
width:100%; width:100%;
position: fixed; position: fixed;
top:140rpx; top:calc(var(--status-bar-height) + 44px);
} }
// //
.tabs { .tabs {
@ -447,7 +470,7 @@ $padding-small: 10px;
.article-list { .article-list {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
top:240rpx; top:calc(var(--status-bar-height) + 44px + 100rpx);
padding-bottom: 0rpx; padding-bottom: 0rpx;
.article-item { .article-item {
background-color: $white; background-color: $white;

View File

@ -53,7 +53,7 @@
</div> </div>
</div> </div>
<!-- 输入框上按钮组 --> <!-- 输入框上按钮组 -->
<div class="msg-button-group"> <!-- <div class="msg-button-group">
<div <div
@tap="handleAudioVisible" @tap="handleAudioVisible"
v-if="!(isWeb || isHarmonyOs || isGroupSend)" v-if="!(isWeb || isHarmonyOs || isGroupSend)"
@ -78,11 +78,11 @@
<div class="msg-input-button"> <div class="msg-input-button">
<Icon @tap="handleSendMoreVisible" type="send-more" :size="20" /> <Icon @tap="handleSendMoreVisible" type="send-more" :size="20" />
</div> </div>
<!-- <div class="msg-input-button"> <div class="msg-input-button">
<Icon @tap="handleSetting" type="icon-shezhi" :size="20" /> <Icon @tap="handleSetting" type="icon-shezhi" :size="20" />
</div> --> </div>
</div> </div> -->
<div v-if="inputVisible" class="msg-input"> <div class="msg-input">
<!-- 当从表情面板切换到文字输入时直接唤起键盘会导致input框滚动消失故此处需要用EmojiInput兼容下保证先隐藏表情面板再弹出键盘 --> <!-- 当从表情面板切换到文字输入时直接唤起键盘会导致input框滚动消失故此处需要用EmojiInput兼容下保证先隐藏表情面板再弹出键盘 -->
<div <div
v-show="showEmojiInput" v-show="showEmojiInput"
@ -96,7 +96,7 @@
}} }}
</div> </div>
</div> </div>
<input <textarea
v-show="!showEmojiInput" v-show="!showEmojiInput"
:focus="isFocus" :focus="isFocus"
class="msg-input-input" class="msg-input-input"
@ -105,9 +105,9 @@
isTeamMute ? t('teamMutePlaceholder') : t('chatInputPlaceHolder') isTeamMute ? t('teamMutePlaceholder') : t('chatInputPlaceHolder')
" "
v-model="inputText" v-model="inputText"
type="text"
:disabled="isTeamMute" :disabled="isTeamMute"
:confirm-hold="true" :confirm-hold="true"
:auto-height="true"
cursor-spacing="20" cursor-spacing="20"
adjust-position="true" adjust-position="true"
confirm-type="send" confirm-type="send"
@ -116,6 +116,12 @@
@input="handleInput" @input="handleInput"
id="msg-input" id="msg-input"
/> />
<view class="rightbox">
<view class="send-text-btn" v-if="inputText" @tap="handleSendTextMsg">
发送
</view>
<up-image @tap="handleSendMoreVisible" v-else :src="addImg" width="68rpx" height="68rpx" ></up-image>
</view>
</div> </div>
<!-- 表情面板 --> <!-- 表情面板 -->
<div v-if="emojiVisible" class="msg-emoji-panel" @click.stop="() => {}"> <div v-if="emojiVisible" class="msg-emoji-panel" @click.stop="() => {}">
@ -157,6 +163,25 @@
</div> </div>
<div class="icon-text">{{ t('albumText') }}</div> <div class="icon-text">{{ t('albumText') }}</div>
</div> </div>
<div class="send-more-panel-item-wrapper">
<div
class="send-more-panel-item"
@tap="(event: any) => handleSendImageMsg('album', event)"
>
<image :src="picImg" mode="widthFix"></image>
</div>
<div class="icon-text">图片</div>
</div>
<div class="send-more-panel-item-wrapper">
<div
class="send-more-panel-item"
@tap="(event: any) => handleSendImageMsg('camera', event)"
>
<image :src="cameraImg" mode="widthFix"></image>
</div>
<div class="icon-text">拍摄</div>
</div>
<div class="send-more-panel-item-wrapper"> <div class="send-more-panel-item-wrapper">
<div <div
class="send-more-panel-item" class="send-more-panel-item"
@ -171,7 +196,7 @@
class="send-more-panel-item" class="send-more-panel-item"
@tap="(event: any) => handleCustom('hj', event)" @tap="(event: any) => handleCustom('hj', event)"
> >
<image :src="quickImg" mode="widthFix"></image> <image :src="hjImg" mode="widthFix"></image>
</div> </div>
<div class="icon-text">患教</div> <div class="icon-text">患教</div>
</div> </div>
@ -297,6 +322,7 @@ import { pathToBase64 } from "image-tools";
import { handleNoPermission } from '@/utils/im/permission' import { handleNoPermission } from '@/utils/im/permission'
import { customNavigateTo } from '@/utils/im/customNavigate' import { customNavigateTo } from '@/utils/im/customNavigate'
import MessageOneLine from '@/components/MessageOneLine.vue' import MessageOneLine from '@/components/MessageOneLine.vue'
import addImg from '@/static/addIcon.png';
import { import {
isAndroidApp, isAndroidApp,
stopAllAudio, stopAllAudio,
@ -320,6 +346,9 @@ import quickImg from '@/static/quck_message.png'
import chuzhenImg from '@/static/outpatient_true.png' import chuzhenImg from '@/static/outpatient_true.png'
import mallImg from '@/static/ytx_chattingfooter_shopping.png' import mallImg from '@/static/ytx_chattingfooter_shopping.png'
import hospitalImg from '@/static/ytx_chatting_hospital.png' import hospitalImg from '@/static/ytx_chatting_hospital.png'
import hjImg from '@/static/huanjiao.png'
import picImg from '@/static/im_icon_images_on.png'
import cameraImg from '@/static/im_icon_camera_on.png'
import {onShow,onUnload} from '@dcloudio/uni-app' import {onShow,onUnload} from '@dcloudio/uni-app'
import { import {
@ -344,6 +373,7 @@ let expert_name=userInfo.realName;
let patient_uuid=''; let patient_uuid='';
const articleInfo=ref({}); const articleInfo=ref({});
const videoInfo=ref({}); const videoInfo=ref({});
const textMsg=ref('');
const patientListByGBK = async () => { const patientListByGBK = async () => {
const res = await api.patientListByGBK(); const res = await api.patientListByGBK();
@ -807,13 +837,14 @@ const handleAudioVisible = () => {
} }
/** 发送图片消息 */ /** 发送图片消息 */
const handleSendImageMsg = () => { const handleSendImageMsg = (type) => {
if (isTeamMute.value) return if (isTeamMute.value) return
stopAllAudio() stopAllAudio()
uni.$emit(events.CLOSE_PANEL)
uni.chooseImage({ uni.chooseImage({
count: 1, count: 1,
//sizeType: ['compressed'], //sizeType: ['compressed'],
sourceType: ['album','camera'], sourceType: [type],
crop: { crop: {
width: 800, width: 800,
height:800, height:800,
@ -1372,23 +1403,51 @@ onUnmounted(() => {
.msg-input-wrapper { .msg-input-wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: #eff1f3; background-color: #fff;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
z-index: 999; z-index: 999;
} }
.rightbox{
width:100rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 10rpx;
.send-text-btn{
display: flex;
align-items: center;
justify-content: center;
background: #f0f0f0;
padding: 0rpx 20rpx;
height:38px;
border: 1px solid #CCC;
border-radius: 10rpx;
margin-top:-2rpx;
font-size: 28rpx;
color: #666;
border-radius: 10rpx;
}
}
.msg-input { .msg-input {
overflow-x: hidden; overflow-x: hidden;
padding: 7px; padding: 7px;
background-color: #eff1f3; display: flex;
align-items: flex-end;
background-color: #fff;
&-input { &-input {
background-color: #fff; background-color: #fff;
height: 40px; min-height: 40px;
max-height: 120px;
overflow-y: auto;
line-height: 1.5;
border-bottom: 1rpx solid #CCC;
flex: 1;
font-size: 16px; font-size: 16px;
padding: 0 12px; padding: 8px 12px;
border-radius: 6px; border-radius: 6px;
margin-bottom: 5px; margin-bottom: 5px;
box-sizing: border-box;
&::placeholder { &::placeholder {
padding: 0 12px; padding: 0 12px;

View File

@ -34,16 +34,18 @@
<view class="card" > <view class="card" >
<view class="card-body"> <view class="card-body">
<view class="leftcircel"></view>
<view class="rightcircel"></view>
<view class="card-header"> <view class="card-header">
<text class="label">{{ item.realname.split(',').length }}位患者</text> <text class="label">{{ item.realname.split(',').length }}位患者</text>
<view class="close" @click="delGroupSendMsg(item)"> <view class="close" @click="delGroupSendMsg(item)">
<text>×</text> <text>×</text>
</view> </view>
</view> </view>
<view class="line" > <view class="line name" >
{{ item.realname }} {{ item.realname }}
</view> </view>
<view class="divider"></view>
<view class="line phone" v-if="item.msg_type==1"> <view class="line phone" v-if="item.msg_type==1">
{{ item.msg_content }} {{ item.msg_content }}
</view> </view>
@ -52,7 +54,7 @@
</view> </view>
<view class="line phone" v-if="item.msg_type==3" @click="onDetail(item)"> <view class="line phone" v-if="item.msg_type==3" @click="onDetail(item)">
<view class="custom"> <view class="custom">
<view class="title">{{JSON.parse(item.msg_content).summary }}</view> <view class="title">{{JSON.parse(item.msg_content).topic }}</view>
<view class="row"> <view class="row">
<view class="left">{{ docUrl+JSON.parse(item.msg_content).path }}</view> <view class="left">{{ docUrl+JSON.parse(item.msg_content).path }}</view>
<view class="right"> <view class="right">
@ -292,7 +294,7 @@ onMounted(() => {
}) })
onShow(() => { onShow(() => {
listGroupSendMsg() listGroupSendMsg(true)
}) })
const onBack = () => { const onBack = () => {
uni.navigateBack() uni.navigateBack()
@ -466,7 +468,7 @@ $red: #D32F2F;
// //
.scroll-container { .scroll-container {
position: fixed; position: fixed;
top: 180rpx; top: calc(var(--status-bar-height) + 62px);
width:100%; width:100%;
bottom: 107rpx; bottom: 107rpx;
@ -475,7 +477,7 @@ $red: #D32F2F;
.time-row { .time-row {
text-align: center; text-align: center;
font-size: 32rpx; font-size: 32rpx;
padding: 32rpx 0; padding: 8rpx 0;
color: #333; color: #333;
} }
@ -503,8 +505,9 @@ $red: #D32F2F;
margin: 24rpx 0; margin: 24rpx 0;
background: #fff; background: #fff;
border-radius: 16rpx; border-radius: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06);
overflow: hidden; overflow: hidden;
border-top:12rpx solid #8B2316;
.card-header { .card-header {
position: relative; position: relative;
padding: 28rpx 28rpx 12rpx 28rpx; padding: 28rpx 28rpx 12rpx 28rpx;
@ -512,10 +515,10 @@ $red: #D32F2F;
.label { font-size: 32rpx; color: #333; } .label { font-size: 32rpx; color: #333; }
.close { .close {
position: absolute; position: absolute;
right: -30rpx; right: -13rpx;
top: -36rpx; top: -28rpx;
width: 100rpx; width: 80rpx;
height: 100rpx; height: 80rpx;
border-bottom-left-radius: 100rpx; border-bottom-left-radius: 100rpx;
background: #8B2316; background: #8B2316;
display: flex; align-items: center; justify-content: center; display: flex; align-items: center; justify-content: center;
@ -523,26 +526,56 @@ $red: #D32F2F;
} }
} }
.card-body { .card-body {
padding: 20rpx 28rpx 8rpx 28rpx; position: relative;
.line { font-size: 32rpx; color: #333; padding: 16rpx 0; } .leftcircel{
.phone { font-size: 36rpx; } position: absolute;
left: -20rpx;;
top: 156rpx;
width: 40rpx;
height: 40rpx;
z-index: 2;
background: #f5f5f5;
border-radius: 50%;
}
.rightcircel{
position: absolute;
right: -20rpx;
z-index: 2;
top: 156rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: #f5f5f5;
}
.line { font-size: 32rpx; color: #666; padding: 16rpx 0; }
.divider{
height: 2rpx;
background: #eee;
margin: 10rpx 0;
}
.name{
padding:20rpx 28rpx;
}
.phone {
word-break: break-all;
font-size: 36rpx;color:#000;
padding: 0 28rpx 20rpx;
}
} }
.card-footer { .card-footer {
display: flex; display: flex;
background:#f1ebeb;
justify-content: flex-end; justify-content: flex-end;
padding: 16rpx 28rpx 28rpx 28rpx; padding: 18rpx 28rpx 18rpx 28rpx;
.btn-outline { .btn-outline {
border: 2rpx solid $red; border: 2rpx solid #8B2316;
color: $red; color: #8B2316;
padding: 10rpx 24rpx; padding: 6rpx 16rpx;
border-radius: 28rpx; border-radius: 28rpx;
font-size: 28rpx; font-size: 24rpx;
} }
} }
} }
.spacer { height: 200rpx; }
.bottom-bar { .bottom-bar {
position: fixed; position: fixed;
left: 0; right: 0; bottom: 0; left: 0; right: 0; bottom: 0;

View File

@ -23,7 +23,10 @@
<!-- 警告提示区域 --> <!-- 警告提示区域 -->
<view class="warning-section"> <view class="warning-section">
<view class="warning-icon">🔔</view> <view class="iconbox">
<uni-icons type="notification" size="24" color="#e65100"></uni-icons>
</view>
<view class="bar"></view>
<text class="warning-text" <text class="warning-text"
>若群发消息选择患者过多(建议少于200人), 部分患者可能将延迟收到消息</text >若群发消息选择患者过多(建议少于200人), 部分患者可能将延迟收到消息</text
> >
@ -221,13 +224,27 @@ $red: #d32f2f;
// //
.warning-section { .warning-section {
.bar{
width: 2rpx;
height: 60rpx;
margin:0 30rpx;
background: #CCC;
}
.iconbox{
display: flex;
align-items: center;
justify-content: center;
}
display: flex; display: flex;
align-items: flex-start; align-items: center;
padding: 24rpx 30rpx; padding: 24rpx 22rpx;
background: #fff8e1; background: #fff;
border-left: 6rpx solid #ff9800; top: calc(var(--status-bar-height) + 44px);
margin: 20rpx 30rpx; position: fixed;
margin-top: calc(var(--status-bar-height) + 64px); left: 0;
right: 0;
border-radius: 8rpx; border-radius: 8rpx;
.warning-icon { .warning-icon {
@ -238,7 +255,8 @@ $red: #d32f2f;
.warning-text { .warning-text {
font-size: 26rpx; font-size: 26rpx;
color: #e65100; color: #666;
line-height: 1.5; line-height: 1.5;
flex: 1; flex: 1;
} }
@ -246,12 +264,17 @@ $red: #d32f2f;
// //
.message-preview-card { .message-preview-card {
top: calc(var(--status-bar-height) + 200rpx);
position: fixed;
left: 0;
right: 0;
margin: 30rpx; margin: 30rpx;
background: #fff; background: #fff;
border-radius: 16rpx; border-radius: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06); box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
overflow: hidden; overflow: hidden;
border-top: 6rpx solid $red; border-top: 11rpx solid $red;
.card-header { .card-header {
position: relative; position: relative;
@ -259,6 +282,7 @@ $red: #d32f2f;
.label { .label {
font-size: 32rpx; font-size: 32rpx;
padding:0 28rpx;
color: #333; color: #333;
} }
@ -287,8 +311,8 @@ $red: #d32f2f;
.patient-list { .patient-list {
.patient-name { .patient-name {
font-size: 32rpx; font-size: 30rpx;
color: #333; color: #666;
line-height: 1.6; line-height: 1.6;
} }
} }

View File

@ -257,6 +257,7 @@ if(res.code == 200){
title: '修改成功', title: '修改成功',
icon: 'none' icon: 'none'
}) })
uni.$emit('updateOutPatient')
uni.navigateBack() uni.navigateBack()
} }
} }
@ -296,7 +297,7 @@ const confirmPublish = () => {
return return
} }
if (!selectedLocation.value) { if (!selectedLocation.value.uuid) {
uni.showToast({ uni.showToast({
title: '请选择门诊地点', title: '请选择门诊地点',
icon: 'none' icon: 'none'

View File

@ -177,6 +177,15 @@
</view> </view>
</uni-popup> </uni-popup>
</view> </view>
<unidialog
:visible="noticeVisible"
:content="content"
@close="noticeCancel"
@confirm="noticeConfirm"
>
</unidialog>
</template> </template>
<script setup> <script setup>
@ -190,13 +199,17 @@ import empty from '@/components/empty/empty.vue'
import sendImg from '@/static/send_feed.png' import sendImg from '@/static/send_feed.png'
import dateBg from '@/static/data_sign.png' import dateBg from '@/static/data_sign.png'
import editImg from '@/static/edit_icon.png' import editImg from '@/static/edit_icon.png'
import unidialog from '@/components/dialog/dialog.vue'
import deleteImg from '@/static/delete_icon.png' import deleteImg from '@/static/delete_icon.png'
import sinaImg from "@/static/share_sina.png" import sinaImg from "@/static/share_sina.png"
import wxImg from "@/static/share_weixin.png" import wxImg from "@/static/share_weixin.png"
import friendImg from "@/static/share_wxc.png" import friendImg from "@/static/share_wxc.png"
import logoImg from "@/static/weiboShare.png" import logoImg from "@/static/weiboShare.png"
const isEdit = ref(false);
const noticeVisible = ref(false);
const SHARE_TITLE = ref('医生门诊详情') const SHARE_TITLE = ref('医生门诊详情')
const SHARE_SUMMARY = '肝胆相照®肝胆病在线服务平台' const SHARE_SUMMARY = '肝胆相照®肝胆病在线服务平台'
const content = ref('您的出/停诊信息有改动,是否通知您的患者?');
const page=ref(1); const page=ref(1);
const isRefreshing = ref(false) const isRefreshing = ref(false)
const outPatientLoading = ref(false) const outPatientLoading = ref(false)
@ -204,7 +217,7 @@ const hasMoreOutPatient = ref(true)
const addressList = ref([]); const addressList = ref([]);
const shareRef = ref() const shareRef = ref()
const shareLink = ref('') const shareLink = ref('')
const patient_user_uuid = ref('');
// //
const from = ref(''); const from = ref('');
const currentTab = ref('suspension') const currentTab = ref('suspension')
@ -214,6 +227,32 @@ const note = ref('')
const outpatientSchedules = ref([]) const outpatientSchedules = ref([])
const outPatientList = ref([]); const outPatientList = ref([]);
const patientListByGBK=()=>{
api.patientListByGBK().then(res=>{
if(res.code==1){
console.log(res.data)
for(let i=0;i<res.data.length;i++){
if(patient_user_uuid.value){
patient_user_uuid.value += res.data[i].uuid+',';
}else{
patient_user_uuid.value += res.data[i].uuid;
}
}
}
})
}
const noticeConfirm = () => {
noticeVisible.value = false;
publishOutPatient();
}
const noticeCancel=()=>{
noticeVisible.value = false;
if(!from.value){
plus.runtime.quit();
}else{
uni.navigateBack();
}
}
onBackPress(() => { onBackPress(() => {
if(!from.value){ if(!from.value){
plus.runtime.quit(); plus.runtime.quit();
@ -233,6 +272,7 @@ onLoad((options) => {
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
patientListByGBK();
}); });
const getListOutPatient = async (reset = false) => { const getListOutPatient = async (reset = false) => {
if (outPatientLoading.value) return; if (outPatientLoading.value) return;
@ -303,6 +343,18 @@ const goSite = () => {
url: '/pages_chat/addAddress/addAddress' url: '/pages_chat/addAddress/addAddress'
}) })
} }
const publishOutPatient = async (uuid) => {
const res = await api.publishOutPatient({
msg_type:'5',
msg_content:'',
patient_user_uuid:patient_user_uuid.value
})
if(!from.value){
plus.runtime.quit();
}else{
uni.navigateBack();
}
}
const getStopReason = (type) => { const getStopReason = (type) => {
const reasonMap = { const reasonMap = {
1: '出差', 1: '出差',
@ -350,11 +402,15 @@ const getTypeText = (type) => {
} }
// //
const goBack = () => { const goBack = () => {
if(!from.value){ if(isEdit.value){
plus.runtime.quit(); noticeVisible.value = true;
}else{ }else{
uni.navigateBack(); if(!from.value){
} plus.runtime.quit();
}else{
uni.navigateBack();
}
}
} }
const shareToggle = () => { const shareToggle = () => {
@ -496,9 +552,13 @@ const addNew = () => {
} }
onShow(() => { onShow(() => {
console.log('onShow')
getStopOutPatientList(); getStopOutPatientList();
getListOutPatient(true); getListOutPatient(true);
fetchList(); fetchList();
uni.$on('updateOutPatient',()=>{
isEdit.value = true;
})
}) })
@ -525,6 +585,11 @@ const deleteAnnouncement = async (uuid) => {
title: '删除成功', title: '删除成功',
icon: 'none' icon: 'none'
}); });
if(stopOutPatientList.value.length-1>0){
isEdit.value=true;
}else{
isEdit.value=false;
}
getStopOutPatientList(); getStopOutPatientList();
} else { } else {
uni.showToast({ uni.showToast({
@ -601,6 +666,11 @@ const deleteSchedule = async (uuid) => {
const deleteRes = await api.deleteOutPatient({ uuid }); const deleteRes = await api.deleteOutPatient({ uuid });
if (deleteRes.code === 200) { if (deleteRes.code === 200) {
outPatientList.value = outPatientList.value.filter(item => item.uuid !== uuid); outPatientList.value = outPatientList.value.filter(item => item.uuid !== uuid);
if(outPatientList.value.length>0){
isEdit.value=true;
}else{
isEdit.value=false;
}
uni.showToast({ uni.showToast({
title: '删除成功', title: '删除成功',
icon: 'none' icon: 'none'

View File

@ -2,13 +2,13 @@
<view class="patient-group-page"> <view class="patient-group-page">
<!-- 头部 --> <!-- 头部 -->
<uni-nav-bar <view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left" left-icon="left"
title="患者分组" title="患者分组"
@clickLeft="goBack" @clickLeft="goBack"
fixed
color="#8B2316" color="#8B2316"
height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> >
@ -16,6 +16,7 @@
<view class="save-text" @click="confirmGroup">确定({{ totalSelectedCount }})</view> <view class="save-text" @click="confirmGroup">确定({{ totalSelectedCount }})</view>
</template> </template>
</uni-nav-bar> </uni-nav-bar>
</view>
<!-- 筛选排序栏 --> <!-- 筛选排序栏 -->
<!-- <view class="filter-sort-bar"> <!-- <view class="filter-sort-bar">
<view class="sort-section" @click="toggleGroupSort"> <view class="sort-section" @click="toggleGroupSort">
@ -82,9 +83,16 @@
<view class="patient-list" v-if="openGroups[gi]"> <view class="patient-list" v-if="openGroups[gi]">
<view class="patient-item" v-for="(patient, index) in (group.patientList || [])" :key="patient.uuid || index"> <view class="patient-item" v-for="(patient, index) in (group.patientList || [])" :key="patient.uuid || index">
<view class="patient-avatar"> <view class="namebox">
<up-image :src="docUrl + patient.photo" width="80rpx" height="80rpx" mode="aspectFill"></up-image> <view class="patient-avatar">
<up-image :src="patient._avatarLoadError ? defaultImg : getPatientAvatar(patient)" width="80rpx" height="80rpx" mode="aspectFill" @error="handlePatientAvatarError(patient)"></up-image>
</view>
<view class="name">
<view class="real">{{ patient.realName}}</view>
<view class="nick" v-if="patient.nickname">{{ patient.nickname}}</view>
</view>
</view> </view>
<view class="patient-checkbox" @click.stop="togglePatientSelection(gi, patient, index)"> <view class="patient-checkbox" @click.stop="togglePatientSelection(gi, patient, index)">
<view class="checkbox" :class="{ 'checked': isPatientSelected(gi, patient, index) }"> <view class="checkbox" :class="{ 'checked': isPatientSelected(gi, patient, index) }">
<uni-icons v-if="isPatientSelected(gi, patient, index)" type="checkmarkempty" color="#8B2316" size="16"></uni-icons> <uni-icons v-if="isPatientSelected(gi, patient, index)" type="checkmarkempty" color="#8B2316" size="16"></uni-icons>
@ -103,12 +111,13 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted,nextTick} from 'vue';
import { onShow,onLoad } from "@dcloudio/uni-app"; import { onShow,onLoad } from "@dcloudio/uni-app";
import upImg from "@/static/triangle_green_theme.png" import upImg from "@/static/triangle_green_theme.png"
import downImg from "@/static/triangle_normal.png" import downImg from "@/static/triangle_normal.png"
import groupRightImg from "@/static/groupright_big.png" import groupRightImg from "@/static/groupright_big.png"
import groupDownImg from "@/static/groupup_big.png" import groupDownImg from "@/static/groupup_big.png"
import defaultImg from "@/static/default.png"
import api from '@/api/api.js'; import api from '@/api/api.js';
import docUrl from '@/utils/docUrl' import docUrl from '@/utils/docUrl'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@ -161,7 +170,11 @@ const selectedPatientCount = computed(() => {
// //
const totalSelectedCount = computed(() => selectedPatientCount.value); const totalSelectedCount = computed(() => selectedPatientCount.value);
onShow(() => { onLoad(() => {
uni.showLoading({
title: '加载中...',
mask: true
});
fetchGroupList(); fetchGroupList();
}); });
@ -183,6 +196,9 @@ const fetchGroupList = async () => {
// //
updateGroupSelectionByPatients(idx); updateGroupSelectionByPatients(idx);
}); });
nextTick(() => {
uni.hideLoading();
});
} }
}; };
@ -268,6 +284,10 @@ const chooseGroupSort = (type) => {
}; };
const confirmGroup = () => { const confirmGroup = () => {
// //
if(totalSelectedCount.value > 200){
uni.showToast({ title: '选择人数不能超过200', icon: 'none' });
return;
}
const idSet = new Set(); const idSet = new Set();
const dedupPatients = []; const dedupPatients = [];
groups.value.forEach((group, gi) => { groups.value.forEach((group, gi) => {
@ -341,9 +361,35 @@ const clearSelection = () => {
selectedPatients.value = {}; selectedPatients.value = {};
}; };
const getPatientAvatar = (patient) => {
if (!patient) return defaultImg;
return patient.photo ? (docUrl + patient.photo) : defaultImg;
};
const handlePatientAvatarError = (patient) => {
if (!patient) return;
patient._avatarLoadError = true;
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.namebox{
display: flex;
align-items: center;
.name{
margin-left: 16rpx;
}
.real{
color: #333;
font-size: 28rpx;
}
.nick{
color:#666;
font-size: 26rpx;
margin-top: 2rpx;
}
}
.patient-group-page { .patient-group-page {
min-height: 100vh; min-height: 100vh;
background-color: #f5f5f5; background-color: #f5f5f5;
@ -578,7 +624,7 @@ const clearSelection = () => {
} }
.patient-list-section { .patient-list-section {
top: 180rpx; top:calc(var(--status-bar-height) + 44px);
width:100%; width:100%;
bottom:120rpx; // bottom:120rpx; //
position: fixed; position: fixed;

View File

@ -159,9 +159,7 @@
uni.showToast({ title: '请输入内容', icon: 'none' }) uni.showToast({ title: '请输入内容', icon: 'none' })
return return
} }
replies.value = [text, ...replies.value] addQuickReply();
uni.setClipboardData({ data: text, showToast: false })
uni.showToast({ title: '已添加并复制', icon: 'none' })
onCancel() onCancel()
} }

View File

@ -1,5 +1,8 @@
<template> <template>
<view class="search-header"> <view class="navbox">
<view class="status_bar"></view>
<view class="search-header">
<view class="status_bar"></view>
<view class="back-icon" @click="goBack"> <view class="back-icon" @click="goBack">
<uni-icons type="left" size="30" color="#8B2316"></uni-icons> <uni-icons type="left" size="30" color="#8B2316"></uni-icons>
</view> </view>
@ -21,6 +24,7 @@
<text class="cancel-text">搜索</text> <text class="cancel-text">搜索</text>
</view> </view>
</view> </view>
</view>
<view class="page"> <view class="page">
<!-- <view class="top"> <!-- <view class="top">
@ -111,15 +115,26 @@
</view> </view>
<!-- 空状态 --> <!-- 空状态 -->
<view class="empty-state" v-if="articleList.length === 0 && !loading"> <!-- <view class="empty-state" v-if="articleList.length === 0 && !loading">
<uni-icons type="article" size="80" color="#cccccc"></uni-icons> <uni-icons type="article" size="80" color="#cccccc"></uni-icons>
<text class="empty-text">暂无文章</text> <text class="empty-text">暂无文章</text>
</view> </view> -->
<empty v-if="articleList.length === 0 && !loading" :emptyDesc="'暂无文章'"></empty>
</scroll-view> </scroll-view>
<!-- 底部导航栏 --> <!-- 底部导航栏 -->
</view> </view>
<unidialog
:visible="noticeVisible"
:title="'是否确定发送此患教科普'"
:content="content"
:confirmText="'发送'"
:cancelText="'取消'"
@close="noticeVisible=false"
@confirm="noticeConfirm"
>
</unidialog>
</template> </template>
<script setup> <script setup>
@ -130,9 +145,22 @@
import filter from "@/static/cb_screen_no.png" import filter from "@/static/cb_screen_no.png"
import filterOn from "@/static/cb_screen_yes.png" import filterOn from "@/static/cb_screen_yes.png"
import api from "@/api/api.js" import api from "@/api/api.js"
import empty from "@/components/empty/empty.vue"
import docUrl from '@/utils/docUrl.js'; import docUrl from '@/utils/docUrl.js';
import navTo from '@/utils/navTo.js'; import navTo from '@/utils/navTo.js';
import navBar from '@/components/navBar/navBar.vue'; import unidialog from '@/components/dialog/dialog.vue'
//import navBar from '@/components/navBar/navBar.vue';
const content=ref('');
const noticeVisible=ref(false);
const articeItem=ref(null);
const noticeConfirm=()=>{
noticeVisible.value=false;
content.value='';
uni.$emit('articelItem', articeItem.value);
uni.navigateBack({
delta:2
});
}
const type=ref(1) const type=ref(1)
@ -187,10 +215,9 @@
try { try {
const page = isRefresh ? 1 : currentPage.value; const page = isRefresh ? 1 : currentPage.value;
const res = await api.polularScienceArticleListByKeywordsNew({ const res = await api.polularScienceArticleListIndexNew({
keywords: keywords.value, topic: keywords.value,
page: page, page: page
type: type.value
}); });
if (res && res.code === 200 && res.data) { if (res && res.code === 200 && res.data) {
@ -281,9 +308,9 @@
const goToDetail = (article) => { const goToDetail = (article) => {
console.log('查看文章详情:', article); console.log('查看文章详情:', article);
// articeItem.value=article;
uni.$emit('articelItem', article); noticeVisible.value=true;
uni.navigateBack(); content.value=article.title;
}; };
// //
@ -440,20 +467,11 @@ $padding-small: 10px;
} }
// //
.search-header { .search-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background-color: #eeeeee; background-color: #eeeeee;
display: flex; display: flex;
height:140rpx; height:44px;
align-items: center; align-items: center;
padding:0 30rpx; padding:0 30rpx 0 8rpx;
gap: 20rpx;
.search-input-wrapper { .search-input-wrapper {
flex: 1; flex: 1;
display: flex; display: flex;
@ -485,7 +503,7 @@ $padding-small: 10px;
padding: 10rpx 20rpx; padding: 10rpx 20rpx;
background:#8B2316; background:#8B2316;
border-radius: 10rpx; border-radius: 10rpx;
margin-left: 20rpx;
.cancel-text { .cancel-text {
font-size: 28rpx; font-size: 28rpx;
color: #fff; color: #fff;

View File

@ -0,0 +1,894 @@
<template>
<view class="navbox">
<view class="status_bar"></view>
<view class="search-header">
<view class="status_bar"></view>
<view class="back-icon" @click="goBack">
<uni-icons type="left" size="30" color="#8B2316"></uni-icons>
</view>
<view class="search-input-wrapper">
<view class="search-icon">
<uni-icons type="search" size="20" color="#999"></uni-icons>
</view>
<input
class="search-input"
placeholder="请输入搜索内容"
v-model="keywords"
@input="onSearchInput"
@confirm="onSearchConfirm"
confirm-type="search"
/>
</view>
<!-- <view class="type-btn" @click="showAllVideoPopup = true">
<text class="type-btn-text">{{ typeName }}</text>
<uni-icons type="bottom" size="14" color="#8B2316"></uni-icons>
</view> -->
<view class="cancel-btn" @click="goSearch">
<text class="cancel-text">搜索</text>
</view>
</view>
</view>
<view class="page">
<!-- <view class="top">
<view class="filter-bar">
<view class="search-box" @click="goToSearch">
<uni-icons type="search" size="16" color="#999"></uni-icons>
<text class="search-text">搜索</text>
</view>
<view class="divider"></view>
<view class="filter-item active" @click="toggleSort">
<text>{{type==1?'最新':'最热'}}</text>
<up-image v-if="type==1" :src="upImg" width="20rpx" height="26rpx" ></up-image>
<up-image v-else :src="downImg" width="20rpx" height="26rpx" ></up-image>
</view>
<view class="divider"></view>
<view class="filter-item" :class="{ active: isFilterActive }" @click="showFilterPopup">
<text>筛选</text>
<up-image :src="isFilterActive ? filterOn : filter" width="30rpx" height="30rpx" ></up-image>
</view>
</view>
</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>
<!-- 全部视频弹窗复制自 video.vue -->
<view class="all-video-popup" v-if="showAllVideoPopup" @click="closeAllVideoPopup">
<view class="popup-content" @click.stop>
<view class="popup-body">
<view
class="category-item"
:class="{ active: typeUuid === item.uuid }"
v-for="item in videoTypeList"
:key="item.uuid"
@click="selectCategory(item.uuid)"
>
{{ item.name }}
</view>
<view v-if="videoTypeList.length === 0" class="empty-content">
<text>暂无数据</text>
</view>
</view>
</view>
</view>
<!-- 文章列表 -->
<scroll-view
class="article-list"
scroll-y
refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
:lower-threshold="100"
>
<view class="article-item" v-for="(article, index) in articleList" :key="article.uuid || index" @click="goToDetail(article)">
<image class="article-image" :src="docUrl+article.imgpath" mode="aspectFill"></image>
<view class="article-content">
<view class="article-title">{{ article.name }}</view>
<view class="article-author">{{ article.public_name }}</view>
<view class="article-meta">
<!-- <view class="date-tag" v-if="article.isToday">今日</view> -->
<view class="date" >{{ formatDate(article.create_date) }}</view>
<view class="stats">
<view class="stat-item">
<uni-icons type="eye" size="12" color="#999"></uni-icons>
<text>{{ article.readnum }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 加载更多状态 -->
<view class="load-more" v-if="loading">
<uni-icons type="spinner-cycle" size="20" color="#999"></uni-icons>
<text class="load-text">加载中...</text>
</view>
<!-- 没有更多数据 -->
<view class="no-more" v-if="!hasMore && articleList.length > 0">
<text class="no-more-text">没有更多数据了</text>
</view>
<!-- 空状态 -->
<!-- <view class="empty-state" v-if="articleList.length === 0 && !loading">
<uni-icons type="article" size="80" color="#cccccc"></uni-icons>
<text class="empty-text">暂无文章</text>
</view> -->
<empty v-if="articleList.length === 0 && !loading" :emptyDesc="'暂无文章'"></empty>
</scroll-view>
<!-- 底部导航栏 -->
</view>
<unidialog
:visible="noticeVisible"
:title="'是否确定发送此患教科普'"
:content="content"
:confirmText="'发送'"
:cancelText="'取消'"
@close="noticeVisible=false"
@confirm="noticeConfirm"
>
</unidialog>
</template>
<script setup>
import { ref } from 'vue';
import { onShow,onLoad } from "@dcloudio/uni-app";
import upImg from "@/static/cb_up.png"
import downImg from "@/static/cb_up.png"
import filter from "@/static/cb_screen_no.png"
import filterOn from "@/static/cb_screen_yes.png"
import api from "@/api/api.js"
import empty from "@/components/empty/empty.vue"
import docUrl from '@/utils/docUrl.js';
import navTo from '@/utils/navTo.js';
import dayjs from 'dayjs';
import unidialog from '@/components/dialog/dialog.vue'
//import navBar from '@/components/navBar/navBar.vue';
const content=ref('');
const typeUuid=ref('');
const typeName=ref('全部视频')
const noticeVisible=ref(false);
const videoItem=ref(null);
const showAllVideoPopup = ref(false);
const videoTypeList=ref([]);
const noticeConfirm=()=>{
noticeVisible.value=false;
content.value='';
uni.$emit('videoItem', videoItem.value);
uni.navigateBack({
delta:2
});
}
const type=ref(1)
onLoad((options) => {
if(options.typeUuid){
typeUuid.value = options.typeUuid;
}
if(options.typeName){
typeName.value = options.typeName;
}
});
//
const currentPage = ref(1);
const pageSize = ref(20);
const hasMore = ref(true);
const loading = ref(false);
const refreshing = ref(false);
//
const activeTab = ref(0);
const showFilter = ref(false);
const isFilterActive = ref(false);
const toggleSort = () => {
type.value = type.value === 1 ? 2 : 1;
//
currentPage.value = 1;
hasMore.value = true;
polularScienceArticleListByKeywordsNew(true);
};
const goSearch = () => {
if(!keywords.value){
uni.showToast({
title: '请输入搜索内容',
icon: 'none'
});
return;
}
polularScienceArticleListByKeywordsNew(true);
};
const onSearchInput = (e) => {
keywords.value = e.detail.value || '';
};
const onSearchConfirm = () => {
goSearch();
};
const loadGuideTags = async () => {
try {
const res = await api.guideTag({
type:4
});
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 patientVideoNew=async ()=>{
const res=await api.patientVideoNew();
if(res.code==200){
videoTypeList.value=res.data || [];
}
}
const polularScienceArticleListByKeywordsNew = async (isRefresh = false) => {
if (loading.value) return;
loading.value = true;
try {
const page = isRefresh ? 1 : currentPage.value;
const res = await api.patientVideoByKeyWordsNew({
keywords: keywords.value,
page: page,
typeUuid: typeUuid.value,
sort:'2'
});
if (res && res.code === 200 && res.data) {
const newData = res.data.list || [];
if (isRefresh) {
//
articleList.value = newData;
currentPage.value = 1;
} else {
//
//const formattedData = newData.map(item => formatArticleData(item));
articleList.value = [...articleList.value, ...newData];
}
// 使
const pageNumber = Number(res.data.pageNumber || 1);
const totalPage = Number(res.data.totalPage || 1);
hasMore.value = pageNumber < totalPage;
currentPage.value = pageNumber + 1;
console.log('文章数据加载成功:', articleList.value);
}
} catch (error) {
console.error('获取文章列表失败:', error);
uni.showToast({
title: '获取数据失败',
icon: 'error',
duration: 2000
});
} finally {
loading.value = false;
refreshing.value = false;
}
};
//
//
const formatDate = (date) => {
return dayjs(date).format('YYYY-MM-DD');
};
//
const filterTags = ref([]);
//
const articleList = ref([]);
//
const switchTab = (index) => {
if(index==1){
navTo({
url: '/pages_app/patientVideo/patientVideo?from=searchArticle'
})
}else if(index==2){
let url=encodeURIComponent('https://wx.igandan.com/wxPatient/index.htm#/problem?link=share&fromtype=doctor')
navTo({
url: '/pages_app/webview/webview?url='+url
})
}
};
const goToDetail = (video) => {
console.log('查看视频详情:', video);
videoItem.value=video;
noticeVisible.value=true;
content.value=video.name;
};
//
const goToSearch = () => {
uni.navigateTo({
url: '/pages_app/search/search'
});
};
//
const onRefresh = async () => {
refreshing.value = true;
await polularScienceArticleListByKeywordsNew(true);
uni.showToast({
title: '刷新成功',
icon: 'none',
duration: 1500
});
};
//
const onLoadMore = async () => {
if (!hasMore.value || loading.value) return;
await polularScienceArticleListByKeywordsNew(false);
};
const goToPage = (page) => {
console.log('跳转到页面:', page);
//
if (page === 'index') {
uni.switchTab({
url: '/pages/index/index'
});
}
};
//
const showFilterPopup = () => {
showFilter.value = true;
};
const goBack = () => {
uni.navigateBack({
delta:1,
fail(){
uni.redirectTo({
url:'/pages/index/index'
})
}
});
};
const closeAllVideoPopup = () => {
showAllVideoPopup.value = false;
};
const selectCategory = (uuid) => {
typeUuid.value = uuid;
typeName.value = (videoTypeList.value.find(item => item.uuid === uuid) || {}).name || '全部视频';
closeAllVideoPopup();
currentPage.value = 1;
hasMore.value = true;
polularScienceArticleListByKeywordsNew(true);
};
const hideFilterPopup = () => {
showFilter.value = false;
};
const toggleTag = (index) => {
filterTags.value[index].selected = !filterTags.value[index].selected;
};
const resetFilter = () => {
filterTags.value.forEach(tag => {
tag.selected = false;
});
isFilterActive.value = false;
};
const keywords=ref('');
const confirmFilter = () => {
//
const hasSelected = filterTags.value.some(tag => tag.selected);
isFilterActive.value = hasSelected;
//
if (hasSelected) {
const selectedTags = filterTags.value.filter(tag => tag.selected).map(tag => tag.NAME);
console.log('选中的标签:', selectedTags);
keywords.value = selectedTags.join(',');
} else {
keywords.value = '';
}
//
currentPage.value = 1;
hasMore.value = true;
showFilter.value = false;
polularScienceArticleListByKeywordsNew(true);
};
onShow(() => {
polularScienceArticleListByKeywordsNew(true);
loadGuideTags();
patientVideoNew();
});
</script>
<style lang="scss" scoped>
//
$primary-color: #ff6b6b;
$theme-color: #8B2316;
$white: #fff;
$gray-bg: #f5f5f5;
$gray-light: #eee;
$gray-medium: #999;
$gray-dark: #666;
$text-color: #333;
//
$border-radius: 8px;
$border-radius-small: 6px;
$padding: 15px;
$padding-small: 10px;
.page {
background-color: $gray-bg;
height: calc(100vh - 180rpx);
display: flex;
overflow-y: hidden;
flex-direction: column;
//
}
.top{
width:100%;
position: fixed;
top:140rpx;
}
//
.tabs {
background-color: $white;
display: flex;
border-bottom: 1px solid $gray-light;
.tab-item {
flex: 1;
text-align: center;
padding: $padding 0;
font-size: 16px;
color: $gray-dark;
position: relative;
&.active {
color: $theme-color;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 30px;
height: 2px;
}
}
}
}
//
.search-header {
background-color: #eeeeee;
display: flex;
height:44px;
align-items: center;
padding:0 30rpx 0 8rpx;
.search-input-wrapper {
flex: 1;
display: flex;
align-items: center;
background-color: #fff;
border-radius: 20rpx;
padding: 0 20rpx;
height:70rpx;
.search-icon {
margin-right: 15rpx;
}
.search-input {
flex: 1;
height: 80rpx;
font-size: 28rpx;
color: #333;
}
.clear-icon {
margin-left: 15rpx;
padding: 10rpx;
}
}
.cancel-btn {
padding: 10rpx 20rpx;
background:#8B2316;
border-radius: 10rpx;
margin-left: 20rpx;
.cancel-text {
font-size: 28rpx;
color: #fff;
}
}
.type-btn{
display: flex;
align-items: center;
gap: 6rpx;
padding: 10rpx 16rpx;
background: #fff;
border-radius: 10rpx;
border: 1rpx solid #8B2316;
margin-left: 12rpx;
.type-btn-text{
font-size: 24rpx;
color: #8B2316;
max-width: 120rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
//
.filter-bar {
background-color: $white;
padding: $padding-small $padding;
display: flex;
align-items: center;
border-bottom: 1px solid $gray-light;
.search-box {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
gap: 5px;
.search-text {
font-size: 14px;
color: $gray-medium;
}
}
.divider {
width: 1px;
height: 16px;
background-color: $gray-medium;
margin: 0 $padding;
}
.filter-item {
flex:1;
display: flex;
align-items: center;
justify-content: center;
gap: 3px;
font-size: 14px;
color: #999;
transition: all 0.3s ease;
}
}
.filter-item.active{
color: $theme-color;
}
//
.article-list {
position: fixed;
bottom: 0;
top:140rpx;
padding-bottom: 0rpx;
.article-item {
background-color: $white;
margin-bottom: $padding-small;
border-radius: $border-radius;
padding: $padding;
display: flex;
gap: 12px;
.article-image {
width: 125px;
height: 80px;
border-radius: $border-radius-small;
flex-shrink: 0;
}
.article-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.article-title {
font-size: 16px;
color: $text-color;
line-height: 1.4;
margin-bottom: 1px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
.article-author {
font-size: 13px;
color: $gray-dark;
margin-bottom: 8rpx;
}
.article-meta {
display: flex;
justify-content: space-between;
align-items: center;
.date-tag {
background-color: $primary-color;
color: $white;
font-size: 12px;
padding: 2px 6px;
border-radius: 3px;
}
.date {
font-size: 12px;
color: $gray-medium;
}
.stats {
display: flex;
gap: $padding;
.stat-item {
display: flex;
align-items: center;
gap: 3px;
font-size: 12px;
color: $gray-medium;
}
}
}
}
}
//
.load-more {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx;
color: #999;
.load-text {
margin-left: 10rpx;
font-size: 28rpx;
}
}
.no-more {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx;
.no-more-text {
font-size: 28rpx;
color: #999;
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 30rpx;
.empty-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #999;
}
}
}
//
.bottom-nav {
background-color: $white;
display: flex;
border-top: 1px solid $gray-light;
padding: 8px 0;
.nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
font-size: 12px;
color: $gray-medium;
&.active {
color: $primary-color;
}
text {
font-size: 10px;
}
}
}
//
.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:329rpx;
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;
gap: 20rpx;
margin-bottom: 60rpx;
max-height: 65vh;
overflow-y: auto;
.tag-item {
background-color: #f8f8f8;
color: $gray-dark;
padding: 8rpx 24rpx;
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;
}
}
}
}
/* 全部视频弹窗样式(复制自 video.vue */
.all-video-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
align-items: flex-end;
}
.popup-content {
width: 100%;
background-color: #fff;
position: fixed;
top: calc(var(--status-bar-height) + 44px + 116rpx);
overflow-y: scroll;
bottom:0;
}
.popup-body {
height:100%;
}
.category-item {
padding: 30rpx;
font-size: 28rpx;
color: #333;
border-bottom: 1rpx solid #f0f0f0;
background-color: #fff;
transition: background-color 0.2s ease;
}
.category-item:last-child {
border-bottom: none;
}
.category-item.active {
background-color: #f8f8f8;
color: #8B2316;
}
.category-item:active {
background-color: #e8f4fd;
}
.empty-content {
display: flex;
justify-content: center;
align-items: center;
height: 200rpx;
color: #999;
font-size: 28rpx;
}
</style>

View File

@ -1,6 +1,16 @@
<template> <template>
<uni-nav-bar left-icon="left" <view class="navbox">
@clickLeft="goBack" title="患教视频" fixed color="#8B2316" height="180rpx" :border="false" backgroundColor="#eeeeee"></uni-nav-bar> <view class="status_bar"></view>
<uni-nav-bar left-icon="left"
@clickLeft="goBack"
title="患教视频"
color="#8B2316"
:border="false"
rightIcon="search"
@clickRight="goSearch"
rightColor="#8B2316"
backgroundColor="#eeeeee"></uni-nav-bar>
</view>
<view class="video-page"> <view class="video-page">
<!-- Header --> <!-- Header -->
<!-- Fixed Filter Tabs --> <!-- Fixed Filter Tabs -->
@ -135,11 +145,23 @@
</view> </view>
</view> </view>
</view> </view>
<unidialog
:visible="noticeVisible"
:title="'是否确定发送此患教科普'"
:content="content"
:confirmText="'发送'"
:cancelText="'取消'"
@close="noticeVisible=false"
@confirm="noticeConfirm"
>
</unidialog>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, computed,reactive } from 'vue'; import { ref, onMounted, computed,reactive } from 'vue';
import { onShow } from "@dcloudio/uni-app"; import { onShow } from "@dcloudio/uni-app";
import unidialog from '@/components/dialog/dialog.vue';
import api from '@/api/api.js'; import api from '@/api/api.js';
import allImg from "@/static/video_all.png" import allImg from "@/static/video_all.png"
import allOnImg from "@/static/video_select.png" import allOnImg from "@/static/video_select.png"
@ -153,6 +175,15 @@
import navTo from '@/utils/navTo'; import navTo from '@/utils/navTo';
import empty from '@/components/empty/empty.vue'; import empty from '@/components/empty/empty.vue';
const isAllActive=ref(false) const isAllActive=ref(false)
const content=ref('');
const noticeVisible=ref(false);
const videoItem=ref(null);
const noticeConfirm=()=>{
noticeVisible.value=false;
content.value='';
uni.$emit('videoItem', videoItem.value);
uni.navigateBack();
}
// //
const videoList = ref([]); const videoList = ref([]);
const activeTab = ref(1); // "" const activeTab = ref(1); // ""
@ -318,8 +349,10 @@
// //
const playVideo = (video) => { const playVideo = (video) => {
uni.$emit('videoItem', video); videoItem.value=video;
uni.navigateBack(); noticeVisible.value=true;
content.value=video.name;
//uni.navigateBack();
}; };
@ -327,7 +360,7 @@
// //
const goSearch = () => { const goSearch = () => {
uni.navigateTo({ uni.navigateTo({
url: '/pages_app/search/search' url: '/pages_chat/video/searchVideo?typeUuid='+typeUuid.value
}); });
}; };
@ -517,9 +550,9 @@
.scroll-view { .scroll-view {
position: fixed; position: fixed;
bottom: 0rpx; bottom: 0rpx;
height: calc(100vh - 180rpx); /* 固定高度,减去导航栏高度 */ /* 固定高度,减去导航栏高度 */
background-color: #f5f5f5; background-color: #f5f5f5;
top: 172rpx; /* 为导航栏留出空间 */ top: calc(var(--status-bar-height) + 44px + 116rpx); /* 为导航栏留出空间 */
} }
@ -530,7 +563,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content:center; justify-content:center;
padding: 30rpx 30rpx; padding: 20rpx 30rpx;
} }
.tab-item { .tab-item {
@ -563,6 +596,7 @@
max-width:155rpx; max-width:155rpx;
display: inline-block; display: inline-block;
overflow: hidden; overflow: hidden;
margin-top: 4rpx;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
@ -574,7 +608,7 @@
/* Fixed Filter Tabs Container */ /* Fixed Filter Tabs Container */
.filter-tabs-container { .filter-tabs-container {
position: fixed; position: fixed;
top: 180rpx; /* uni-nav-bar height */ top: calc(var(--status-bar-height) + 44px); /* uni-nav-bar height */
left: 0; left: 0;
right: 0; right: 0;
z-index: 40; z-index: 40;
@ -602,10 +636,9 @@
/* Video List */ /* Video List */
.video-list { .video-list {
padding: 0 20rpx; padding: 0 20rpx;
margin-top: 100rpx; /* 为固定的Filter Tabs留出空间 */
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;/* 卡片间距 */
gap: 16rpx; /* 卡片间距 */
} }
.video-item { .video-item {
@ -614,10 +647,13 @@
margin-bottom: 0; margin-bottom: 0;
overflow: hidden; overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1); box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
width: calc(50% - 8rpx); /* 两列布局减半gap */ width: calc((100% - 16rpx) / 2); /* 两列布局中间间距16rpx */
min-width: 300rpx; /* 确保最小宽度 */ min-width: 300rpx; /* 确保最小宽度 */
flex-shrink: 0; /* 防止收缩 */ flex-shrink: 0; /* 防止收缩 */
} }
.video-list .video-item:nth-child(2n+2){
margin-left: 16rpx;
}
.video-thumbnail { .video-thumbnail {
position: relative; position: relative;
@ -667,6 +703,10 @@
.author { .author {
font-size: 24rpx; font-size: 24rpx;
color: #999; color: #999;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 20rpx;
} }
.stats { .stats {
@ -719,8 +759,10 @@
.popup-content { .popup-content {
width: 100%; width: 100%;
background-color: #fff; background-color: #fff;
height:calc(100vh - 272rpx); position: fixed;
top: calc(var(--status-bar-height) + 44px + 116rpx);
overflow-y: scroll; overflow-y: scroll;
bottom:0;
} }
.popup-header { .popup-header {

BIN
static/addIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
static/default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
static/huanjiao.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
static/patientgif.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB