9.19自定义消息,聊天相关页面
This commit is contained in:
parent
b884a8f6fc
commit
ae08b222af
9
App.vue
9
App.vue
@ -331,6 +331,10 @@ export default {
|
||||
|
||||
/** nim sdk 登录 */
|
||||
nim.V2NIMLoginService.login(opts.account, opts.token).then(async () => {
|
||||
console.log("登录成功");
|
||||
nim.V2NIMLoginService.on('onKickedOffline', (res) => {
|
||||
console.log("被踢下线", res);
|
||||
});
|
||||
// #ifdef APP-PLUS
|
||||
/** 初始化音视频通话插件*/
|
||||
nimCallKit.initConfig(
|
||||
@ -345,6 +349,7 @@ export default {
|
||||
if (ret.code != 200) {
|
||||
// callkit init失败
|
||||
} else {
|
||||
|
||||
nimCallKit.login(
|
||||
{
|
||||
account: opts.account,
|
||||
@ -355,6 +360,7 @@ export default {
|
||||
// 登录失败
|
||||
} else {
|
||||
// 登录成功
|
||||
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -396,7 +402,8 @@ export default {
|
||||
console.log("音视频通话插件退出失败", error);
|
||||
}
|
||||
// 退出登录
|
||||
uni.$UIKitNIM.V2NIMLoginService.logout().then(() => {
|
||||
uni.$UIKitNIM.V2NIMLoginService.logout().then((res) => {
|
||||
console.log("退出登录", res);
|
||||
uni.$UIKitStore.destroy();
|
||||
customReLaunch({
|
||||
url: "/pages/Login/index",
|
||||
|
||||
18
api/api.js
18
api/api.js
@ -493,9 +493,21 @@ const api = {
|
||||
meetingV2Video(data){
|
||||
return request('/expertAPI/meetingV2Video', data, 'post', false);
|
||||
},
|
||||
|
||||
|
||||
|
||||
listGroupSendMsg(data){
|
||||
return request('/expertAPI/listGroupSendMsg', data, 'post', false);
|
||||
},
|
||||
addGroupSendMsg4YunXin(data){
|
||||
return request('/expertAPI/addGroupSendMsg4YunXin', data, 'post', false);
|
||||
},
|
||||
quickReplyList(data){
|
||||
return request('/expertAPI/QuickReplyList', data, 'post', false);
|
||||
},
|
||||
addQuickReply(data){
|
||||
return request('/expertAPI/addQuickReply', data, 'post', false);
|
||||
},
|
||||
deleteQuickReply(data){
|
||||
return request('/expertAPI/deleteQuickReply', data, 'post', false);
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
|
||||
60
pages.json
60
pages.json
@ -1047,6 +1047,66 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "groupSend/groupSend",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "uni-app分页",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "patientGroup/patientGroup",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "uni-app分页",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "quickReply/quickReply",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "uni-app分页",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "article/article",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "uni-app分页",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "searchArticle/searchArticle",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "uni-app分页",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "video/video",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "uni-app分页",
|
||||
"app": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "groupMessage/groupMessage",
|
||||
"style": {
|
||||
|
||||
@ -304,7 +304,7 @@
|
||||
url="/pages_app/patientGroup/patientGroup"
|
||||
break;
|
||||
case 'groupMessage':
|
||||
url="/pages_app/groupMessage/groupMessage"
|
||||
url="/pages_chat/groupMessage/groupMessage"
|
||||
break;
|
||||
case 'qrcode':
|
||||
url="/pages_app/myCode/myCode"
|
||||
|
||||
@ -88,7 +88,7 @@
|
||||
const updateSelectedDetail = () => {
|
||||
selectedDetail.value = selectedIds.value.map(id => {
|
||||
const p = patientList.value.find(x => x.uuid === id)
|
||||
return { uuid: id, realName: p?.realName || '', photo: p?.photo || '' }
|
||||
return { uuid: id, realName: p?.realName || '', photo: p?.photo || '',nickName: p?.nickName || '' }
|
||||
})
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@
|
||||
// 如果未选中,则选中
|
||||
selectedIds.value.push(id)
|
||||
const p = patientList.value.find(x => x.uuid === id)
|
||||
selectedDetail.value.push({ uuid: id, realName: p?.realName || '', photo: p?.photo || '' })
|
||||
selectedDetail.value.push({ uuid: id, realName: p?.realName || '', photo: p?.photo || '',nickName: p?.nickName || '' })
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,7 +116,9 @@
|
||||
const pages = getCurrentPages()
|
||||
const curr = pages[pages.length - 1]
|
||||
const ec = curr?.getOpenerEventChannel?.()
|
||||
ec?.emit && ec.emit('onPatientsSelected', payload)
|
||||
ec?.emit && ec.emit('onPatientsSelected', payload);
|
||||
console.log(selectedDetail.value)
|
||||
uni.$emit('selectedChatPatientsSingle', { patients: selectedDetail.value });
|
||||
} catch (e) {}
|
||||
// 兜底:使用本地存储
|
||||
try { uni.setStorageSync('patientsSelectedPayload', payload) } catch (e) {}
|
||||
|
||||
@ -88,7 +88,7 @@
|
||||
const updateSelectedDetail = () => {
|
||||
selectedDetail.value = selectedIds.value.map(id => {
|
||||
const p = patientList.value.find(x => x.uuid === id)
|
||||
return { uuid: id, realName: p?.realName || '', photo: p?.photo || '' }
|
||||
return { uuid: id, realName: p?.realName || '', photo: p?.photo || '',nickname: p?.nickname || '' }
|
||||
})
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@
|
||||
selectedIds.value.push(id)
|
||||
const p = patientList.value.find(x => x.uuid === id)
|
||||
selectedDetail.value.push({ uuid: id, realName: p?.realName || '', photo: p?.photo || '' })
|
||||
let payload = { uuid: id, realName: p?.realName || '', photo: p?.photo || '' }
|
||||
let payload = { uuid: id, realName: p?.realName || '', photo: p?.photo || '',nickname: p?.nickname || '' }
|
||||
const pages = getCurrentPages()
|
||||
const curr = pages[pages.length - 1]
|
||||
const ec = curr?.getOpenerEventChannel?.()
|
||||
|
||||
668
pages_chat/article/article.vue
Normal file
668
pages_chat/article/article.vue
Normal file
@ -0,0 +1,668 @@
|
||||
<template>
|
||||
<navBar title="患教学堂"></navBar>
|
||||
<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.DES }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="filter-buttons">
|
||||
<view class="btn-reset" @click="resetFilter">重置</view>
|
||||
<view class="btn-confirm" @click="confirmFilter">确定</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="article.image" mode="aspectFill"></image>
|
||||
<view class="article-content">
|
||||
<view class="article-title">{{ article.title }}</view>
|
||||
<view class="article-meta">
|
||||
<view class="date-tag" v-if="article.isToday">今日</view>
|
||||
<view class="date" v-else>{{ article.date }}</view>
|
||||
<view class="stats">
|
||||
<view class="stat-item">
|
||||
<uni-icons type="eye" size="12" color="#999"></uni-icons>
|
||||
<text>{{ article.views }}</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<uni-icons type="hand-up" size="13" color="#999"></uni-icons>
|
||||
<text>{{ article.likes }}</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>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onShow } 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 docUrl from '@/utils/docUrl.js';
|
||||
import navTo from '@/utils/navTo.js';
|
||||
import navBar from '@/components/navBar/navBar.vue';
|
||||
|
||||
const type=ref(1)
|
||||
|
||||
// 分页相关状态
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(10);
|
||||
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 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 polularScienceArticleListByKeywordsNew = async (isRefresh = false) => {
|
||||
if (loading.value) return;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const page = isRefresh ? 1 : currentPage.value;
|
||||
const res = await api.polularScienceArticleListByKeywordsNew({
|
||||
keywords: keywords.value,
|
||||
page: page,
|
||||
type: type.value
|
||||
});
|
||||
|
||||
if (res && res.code === 200 && res.data) {
|
||||
const newData = res.data.list || [];
|
||||
|
||||
if (isRefresh) {
|
||||
// 下拉刷新:替换数据
|
||||
articleList.value = newData.map(item => formatArticleData(item));
|
||||
currentPage.value = 1;
|
||||
} else {
|
||||
// 上拉加载:追加数据
|
||||
const formattedData = newData.map(item => formatArticleData(item));
|
||||
articleList.value = [...articleList.value, ...formattedData];
|
||||
}
|
||||
|
||||
// 判断是否还有更多数据
|
||||
hasMore.value = newData.length >= pageSize.value;
|
||||
|
||||
if (!isRefresh) {
|
||||
currentPage.value++;
|
||||
}
|
||||
|
||||
console.log('文章数据加载成功:', articleList.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取文章列表失败:', error);
|
||||
uni.showToast({
|
||||
title: '获取数据失败',
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
refreshing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化文章数据
|
||||
const formatArticleData = (item) => {
|
||||
// 安全检查,确保item存在且有所需属性
|
||||
if (!item) return null;
|
||||
|
||||
const submitDate = item.submitDate ? new Date(item.submitDate) : new Date();
|
||||
const today = new Date();
|
||||
const isToday = submitDate.toDateString() === today.toDateString();
|
||||
|
||||
return {
|
||||
uuid: item.uuid || '',
|
||||
title: item.topic || '无标题',
|
||||
summary: item.summary || '',
|
||||
image: item.imgPath ? docUrl + item.imgPath : '/static/liver_knowledge.png',
|
||||
date: isToday ? '今日' : formatDate(submitDate),
|
||||
isToday: isToday,
|
||||
views: item.readnum || 0,
|
||||
likes: item.agreenum || 0,
|
||||
tags: item.tags || '',
|
||||
path: item.path || '',
|
||||
submitDate: item.submitDate || ''
|
||||
};
|
||||
};
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (date) => {
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${month}-${day}`;
|
||||
};
|
||||
// 筛选标签数据
|
||||
const filterTags = ref([]);
|
||||
|
||||
// 文章列表数据
|
||||
const articleList = ref([]);
|
||||
|
||||
// 方法
|
||||
const switchTab = (index) => {
|
||||
|
||||
if(index==1){
|
||||
navTo({
|
||||
url: '/pages_app/patientVideo/patientVideo'
|
||||
})
|
||||
}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 = (article) => {
|
||||
console.log('查看文章详情:', article);
|
||||
// 跳转到详情页
|
||||
uni.$emit('articelItem', article);
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
// 跳转到搜索页面
|
||||
const goToSearch = () => {
|
||||
navTo({
|
||||
url: '/pages_chat/searchArticle/searchArticle'
|
||||
});
|
||||
};
|
||||
|
||||
// 下拉刷新
|
||||
const onRefresh = async () => {
|
||||
refreshing.value = true;
|
||||
await polularScienceArticleListByKeywordsNew(true);
|
||||
uni.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success',
|
||||
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 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.DES);
|
||||
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();
|
||||
});
|
||||
</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 - 140rpx);
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 筛选栏
|
||||
.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:240rpx;
|
||||
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: 80px;
|
||||
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: $padding-small;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
234
pages_chat/chat/message/message-custom.vue
Normal file
234
pages_chat/chat/message/message-custom.vue
Normal file
@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div class="msg-custom" :style="{ fontSize: (fontSize || 16) + 'px' }">
|
||||
<view
|
||||
class="customCell cloumn"
|
||||
v-if="JSON.parse(msg.attachment.raw).gdxz_ext_data == '[肝胆商城]'"
|
||||
@click="goToDetail(JSON.parse(msg.attachment.raw).gdxz_url)"
|
||||
>
|
||||
<view class="title">{{ JSON.parse(msg.attachment.raw).gdxz_title }}</view>
|
||||
<view class="row">
|
||||
<view class="left"> 肝胆相照®肝胆病在线公共服务平台 </view>
|
||||
<view class="right">
|
||||
<image
|
||||
src="http://doc.igandan.com/app/html/img/2016/20160714132557.png"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="customCell cloumn"
|
||||
v-else-if="JSON.parse(msg.attachment.raw).gdxz_ext_data == '[公益咨询]'"
|
||||
@click="goDetail(JSON.parse(msg.attachment.raw).gdxz_id)"
|
||||
>
|
||||
<view class="title">{{ JSON.parse(msg.attachment.raw).gdxz_title }}</view>
|
||||
<view class="row">
|
||||
<view class="left"> 点击查看问题详情 </view>
|
||||
<view class="right">
|
||||
<image
|
||||
src="http://doc.igandan.com/app/html/img/2016/20160714132557.png"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="customCell cloumn"
|
||||
v-else-if="JSON.parse(msg.attachment.raw).gdxz_ext_data == '[门诊公告]'"
|
||||
@click="goToDetail(JSON.parse(msg.attachment.raw).gdxz_url)"
|
||||
>
|
||||
|
||||
<view class="title">{{ JSON.parse(msg.attachment.raw).gdxz_title }}</view>
|
||||
<view class="row">
|
||||
<view class="left"> 肝胆相照®肝胆病在线公共服务平台 </view>
|
||||
<view class="right">
|
||||
<image
|
||||
src="http://doc.igandan.com/app/html/img/2016/20160714132557.png"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="customCell cloumn"
|
||||
v-else-if="
|
||||
JSON.parse(msg.attachment.raw).gdxz_ext_data == '[图文科普]' ||
|
||||
JSON.parse(msg.attachment.raw).gdxz_ext_data == '[视频科普]'
|
||||
"
|
||||
@click="goToDetail(JSON.parse(msg.attachment.raw).gdxz_url)"
|
||||
>
|
||||
<view class="title">{{ JSON.parse(msg.attachment.raw).gdxz_title }}</view>
|
||||
<view class="row">
|
||||
<view class="left">
|
||||
{{ JSON.parse(msg.attachment.raw).gdxz_content }}
|
||||
</view>
|
||||
<view class="right">
|
||||
<image
|
||||
src="http://doc.igandan.com/app/html/img/2016/20160714132557.png"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="customCell cloumn"
|
||||
v-else-if="JSON.parse(msg.attachment.raw).gdxz_ext_data == '[互联网医院]'"
|
||||
@click="goToMiniProgram"
|
||||
>
|
||||
|
||||
<view class="title">肝胆相照互联网医院</view>
|
||||
<view class="row">
|
||||
<view class="left">
|
||||
{{ JSON.parse(msg.attachment.raw).gdxz_content }}
|
||||
</view>
|
||||
<view class="right">
|
||||
<image
|
||||
src="http://doc.igandan.com/app/html/img/2016/20160714132557.png"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/**文本消息组件 */
|
||||
import Icon from "@/components/Icon.vue";
|
||||
// @ts-ignore
|
||||
import UniLink from "@/components/uni-components/uni-link/components/uni-link/uni-link.vue";
|
||||
import { parseText } from "@/utils/im/parseText";
|
||||
import { EMOJI_ICON_MAP_CONFIG } from "@/utils/im/emoji";
|
||||
import { V2NIMMessageForUI } from "@xkit-yx/im-store-v2/dist/types/types";
|
||||
import { t } from "@/utils/im/i18n";
|
||||
import { isHarmonyOs } from "@/utils/im/index";
|
||||
import navTo from "@/utils/navTo";
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
msg: V2NIMMessageForUI;
|
||||
fontSize?: number;
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
const goToDetail = (url: string) => {
|
||||
navTo({
|
||||
url: `/pages_app/webview/webview?url=${encodeURIComponent(url)}`,
|
||||
});
|
||||
};
|
||||
const goDetail = (id: string) => {
|
||||
navTo({
|
||||
url: `/pages_app/consultDetail/consultDetail?uuid=${id}`,
|
||||
});
|
||||
};
|
||||
// 跳转到微信小程序
|
||||
const goToMiniProgram = () => {
|
||||
// 检查是否在App端
|
||||
// #ifdef APP-PLUS
|
||||
// App端跳转小程序的逻辑
|
||||
plus.share.getServices((res) => {
|
||||
let sweixin = null;
|
||||
for (let i in res) {
|
||||
if (res[i].id == "weixin") {
|
||||
sweixin = res[i];
|
||||
}
|
||||
}
|
||||
//唤醒微信小程序
|
||||
if (sweixin) {
|
||||
uni.hideLoading();
|
||||
sweixin.launchMiniProgram({
|
||||
id: "wxc83296720404aa7b", // 小程序的原始ID,微信公众平台设置里有
|
||||
type: 0, //小程序版本 0-正式版; 1-测试版; 2-体验版。
|
||||
path: "Pages/index/index", //小程序的页面,用传的参数在小程序接值判断跳转指定页面
|
||||
});
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序端跳转其他小程序
|
||||
uni.navigateToMiniProgram({
|
||||
appId: "wxc83296720404aa7b",
|
||||
path: "Pages/index/index",
|
||||
extraData: {
|
||||
// 可以传递额外数据给小程序
|
||||
},
|
||||
envVersion: "release",
|
||||
success: (res) => {
|
||||
console.log("跳转小程序成功", res);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log("跳转小程序失败", err);
|
||||
uni.showToast({
|
||||
title: "跳转失败",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
};
|
||||
/**解析文本 */
|
||||
const textArr = parseText(props.msg?.text || "", props.msg?.serverExtension);
|
||||
|
||||
/**unilink 不支持鸿蒙 故提示在浏览器打开链接 */
|
||||
const openInBrowser = (url: string) => {
|
||||
uni.setClipboardData({
|
||||
data: url,
|
||||
showToast: false,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: t("openUrlText"),
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.msg-custom {
|
||||
color: #333;
|
||||
display: flex;
|
||||
text-align: left;
|
||||
overflow-y: auto;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
.customCell {
|
||||
display: flex;
|
||||
}
|
||||
.customCell.cloumn {
|
||||
flex-direction: column;
|
||||
}
|
||||
.customCell .title {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2; /* 注意:这不是标准的CSS属性 */
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.customCell .row {
|
||||
margin-top: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.left {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-right: 20rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2; /* 注意:这不是标准的CSS属性 */
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.right image {
|
||||
width: 100rpx;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -78,9 +78,9 @@
|
||||
<div class="msg-input-button">
|
||||
<Icon @tap="handleSendMoreVisible" type="send-more" :size="20" />
|
||||
</div>
|
||||
<div class="msg-input-button">
|
||||
<!-- <div class="msg-input-button">
|
||||
<Icon @tap="handleSetting" type="icon-shezhi" :size="20" />
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div v-if="inputVisible" class="msg-input">
|
||||
<!-- 当从表情面板切换到文字输入时,直接唤起键盘,会导致input框滚动消失,故此处需要用EmojiInput兼容下,保证先隐藏表情面板,再弹出键盘 -->
|
||||
@ -157,8 +157,54 @@
|
||||
</div>
|
||||
<div class="icon-text">{{ t('albumText') }}</div>
|
||||
</div>
|
||||
<div class="send-more-panel-item-wrapper">
|
||||
<div
|
||||
class="send-more-panel-item"
|
||||
@tap="(event: any) => handleCustom('reply', event)"
|
||||
>
|
||||
<image :src="quickImg" 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) => handleCustom('hj', event)"
|
||||
>
|
||||
<image :src="quickImg" 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) => handleCustom('outpatient', event)"
|
||||
>
|
||||
<image :src="chuzhenImg" 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) => handleCustom('mall', event)"
|
||||
>
|
||||
<image :src="mallImg" 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) => handleCustom('hospital', event)"
|
||||
>
|
||||
<image :src="hospitalImg" mode="widthFix"></image>
|
||||
</div>
|
||||
<div class="icon-text">互联网医院</div>
|
||||
</div> -->
|
||||
<!-- 音频呼叫 -->
|
||||
<div
|
||||
<!-- <div
|
||||
class="send-more-panel-item-wrapper"
|
||||
v-if="
|
||||
isAndroidOrIosApp &&
|
||||
@ -170,10 +216,10 @@
|
||||
<Icon type="icon-audio-call" :size="30"></Icon>
|
||||
</div>
|
||||
<div class="icon-text">{{ t('voiceCallText') }}</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- 视频呼叫 -->
|
||||
<div
|
||||
<!-- <div
|
||||
class="send-more-panel-item-wrapper"
|
||||
v-if="
|
||||
isAndroidOrIosApp &&
|
||||
@ -185,8 +231,8 @@
|
||||
<Icon type="icon-video-call" :size="30"></Icon>
|
||||
</div>
|
||||
<div class="icon-text">{{ t('videoCallText') }}</div>
|
||||
</div>
|
||||
<div
|
||||
</div> -->
|
||||
<!-- <div
|
||||
v-if="isWeb"
|
||||
class="send-more-panel-item-wrapper"
|
||||
@tap="handleSendFileMsg"
|
||||
@ -195,7 +241,7 @@
|
||||
<Icon type="icon-file" :size="30"></Icon>
|
||||
</div>
|
||||
<div class="icon-text">{{ t('fileText') }}</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- @消息相关 popup -->
|
||||
@ -208,12 +254,38 @@
|
||||
>
|
||||
<MentionMemberList :team-id="to"></MentionMemberList>
|
||||
</UniPopup>
|
||||
<!-- 复制自 groupSend 的遮罩与弹窗 -->
|
||||
<view v-if="showModal" class="mask" @tap="closeModal"></view>
|
||||
<view v-if="showModal" class="center-modal">
|
||||
<view class="modal-title">温馨提示</view>
|
||||
<view class="modal-divider"></view>
|
||||
<view class="modal-item" @tap="onSelectSince('article')">
|
||||
<text>图文科普</text>
|
||||
</view>
|
||||
<view class="modal-divider"></view>
|
||||
<view class="modal-item" @tap="onSelectSince('video')">
|
||||
<text>视频科普</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 商城弹窗 -->
|
||||
<view v-if="showMallModal" class="mask" @tap="closeMallModal"></view>
|
||||
<view v-if="showMallModal" class="center-modal">
|
||||
<view class="modal-title">温馨提示</view>
|
||||
<view class="modal-divider"></view>
|
||||
<view class="modal-item" @tap="sendMallMsg">
|
||||
<text>纽娃复合营养素固体饮料</text>
|
||||
</view>
|
||||
<view class="modal-divider"></view>
|
||||
<view class="modal-item" >
|
||||
<text>更多商品正在准备中</text>
|
||||
</view>
|
||||
</view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/** 消息页面输入框组件 */
|
||||
|
||||
import docUrl from '@/utils/docUrl'
|
||||
import Face from './face.vue'
|
||||
import VoicePanel from './voice-panel.vue'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
@ -242,6 +314,11 @@ import Appellation from '@/components/Appellation.vue'
|
||||
import { AT_ALL_ACCOUNT } from '@/utils/im/constants'
|
||||
import { replaceEmoji } from '@/utils/im/index'
|
||||
import { autorun } from 'mobx'
|
||||
import quickImg from '@/static/quck_message.png'
|
||||
import chuzhenImg from '@/static/outpatient_true.png'
|
||||
import mallImg from '@/static/ytx_chattingfooter_shopping.png'
|
||||
import hospitalImg from '@/static/ytx_chatting_hospital.png'
|
||||
import {onShow,onUnload} from '@dcloudio/uni-app'
|
||||
|
||||
import {
|
||||
V2NIMTeam,
|
||||
@ -257,7 +334,30 @@ import {
|
||||
import { V2NIMConst } from '@/utils/im/nim'
|
||||
import { V2NIMMessage } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMMessageService'
|
||||
export type MentionedMember = { accountId: string; appellation: string }
|
||||
|
||||
import navTo from '@/utils/navTo'
|
||||
import api from '@/api/api.js'
|
||||
let userInfo=uni.getStorageSync('userInfo');
|
||||
let expert_uuid=userInfo.uuid;
|
||||
let expert_name=userInfo.realName;
|
||||
let patient_uuid='';
|
||||
const articleInfo=ref({});
|
||||
const videoInfo=ref({});
|
||||
const patientListByGBK = async () => {
|
||||
|
||||
const res = await api.patientListByGBK();
|
||||
if(res.code == 1){
|
||||
//patientList.value = res.data;
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
if (res.data[i].uuid.toLowerCase() === props.to) {
|
||||
patient_uuid = res.data[i].uuid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
conversationType: V2NIMConst.V2NIMConversationType
|
||||
@ -278,6 +378,10 @@ const conversationId =
|
||||
/** 输入框内容 */
|
||||
const inputText = ref('')
|
||||
const extVisible = ref(false)
|
||||
/** 群发选择弹窗 */
|
||||
const showModal = ref(false)
|
||||
/** 商城弹窗 */
|
||||
const showMallModal = ref(false)
|
||||
/** 音频面板flag */
|
||||
const audioPanelVisible = ref(false)
|
||||
/** 发送更多面板flag */
|
||||
@ -314,6 +418,39 @@ const handleCall = (type: number) => {
|
||||
})
|
||||
}
|
||||
}
|
||||
onShow(() => {
|
||||
uni.$on('articelItem', (article) => {
|
||||
console.log('article', article);
|
||||
articleInfo.value = article;
|
||||
senCustomMsg('article');
|
||||
//handleSendTextMsg('article', article);
|
||||
});
|
||||
uni.$on('videoItem', (video) => {
|
||||
videoInfo.value = video;
|
||||
senCustomMsg('video');
|
||||
//handleSendTextMsg('video', video);
|
||||
});
|
||||
uni.$on('quickReply', (reply) => {
|
||||
inputText.value= reply;
|
||||
});
|
||||
});
|
||||
onUnload(() => {
|
||||
uni.$off('articelItem');
|
||||
uni.$off('videoItem');
|
||||
uni.$off('quickReply');
|
||||
});
|
||||
/** 商城弹窗 */
|
||||
let rawStr="{\"gdxz_content\":\"我已入驻肝胆相照互联网医院,复诊购药一站式服务,快来看看吧\",\"gdxz_ext_data\":\"[互联网医院]\",\"gdxz_id\":\"1681174885629431808\",\"gdxz_img\":\"https://img.applets.igandanyiyuan.com/applet/admin/avatar/2023071813261420200708181049.png\",\"gdxz_title\":\"肝胆相照互联网医院\",\"gdxz_type\":\"[互联网医院]\",\"gdxz_url\":\"\"}";
|
||||
const sendMallMsg = () => {
|
||||
showMallModal.value = false;
|
||||
handleSendTextMsg('mall');
|
||||
|
||||
}
|
||||
|
||||
/** 商城弹窗 */
|
||||
const closeMallModal = () => {
|
||||
showMallModal.value = false
|
||||
}
|
||||
|
||||
/** 用于解决表情面板和键盘冲突,导致输入框滚动消失问题 */
|
||||
const showEmojiInput = ref(false)
|
||||
@ -326,6 +463,7 @@ const replyMsg = ref<V2NIMMessageForUI>()
|
||||
/** @ 消息相关 */
|
||||
const ctx = getCurrentInstance()
|
||||
const popupRef = ref(null)
|
||||
|
||||
/** @ 成员列表 */
|
||||
const selectedAtMembers = ref<MentionedMember[]>([])
|
||||
|
||||
@ -381,6 +519,26 @@ const onPopupChange = (e: any) => {
|
||||
uni.$emit(events.HANDLE_MOVE_THROUGH, e.value)
|
||||
}
|
||||
|
||||
/** 打开/关闭群发选择弹窗(与 groupSend 保持一致) */
|
||||
const openModal = () => {
|
||||
showModal.value = true;
|
||||
|
||||
}
|
||||
const closeModal = () => {
|
||||
showModal.value = false
|
||||
}
|
||||
const toggleModal = () => {
|
||||
showModal.value = !showModal.value
|
||||
}
|
||||
const onSelectSince = (type: string) => {
|
||||
showModal.value = false
|
||||
if (type === 'article') {
|
||||
navTo({ url: '/pages_chat/article/article' })
|
||||
} else if (type === 'video') {
|
||||
navTo({ url: '/pages_chat/video/video' })
|
||||
}
|
||||
}
|
||||
|
||||
/** 点击@ 群成员 */
|
||||
const handleMentionItemClick = (member: MentionedMember) => {
|
||||
//@ts-ignore
|
||||
@ -455,19 +613,21 @@ const handleInput = (event: any) => {
|
||||
}
|
||||
|
||||
/** 发送文本消息 */
|
||||
const handleSendTextMsg = () => {
|
||||
const handleSendTextMsg = (type:string) => {
|
||||
if (inputText.value.trim() === '') return
|
||||
const ext = onAtMembersExtHandler()
|
||||
let text = replaceEmoji(inputText.value)
|
||||
let mallText='纽娃复合营养素固体饮料主要成分是:蜂花粉、乳清蛋白粉、灰树花粉、低聚木糖、蚕蛹氨基酸、麦芽粉、薏苡仁粉、烟酸、磷脂,以及其他调味品、辅助原料。科学配比制成,含有丰富的蛋白质、氨基酸、维生素、微量元素及其他营养元素,点击链接了解详情。'
|
||||
let text = type==='mall'?mallText:replaceEmoji(inputText.value)
|
||||
const textMsg = uni.$UIKitNIM.V2NIMMessageCreator.createTextMessage(text)
|
||||
|
||||
let serverExtension={"gdxz_nickName":"测试","gdxz_sessionType":"general"};
|
||||
uni.$UIKitStore.msgStore
|
||||
.sendMessageActive({
|
||||
msg: textMsg as unknown as V2NIMMessage,
|
||||
conversationId,
|
||||
serverExtension: selectedAtMembers.value.length && (ext as any),
|
||||
serverExtension:selectedAtMembers.value.length && (ext as any),
|
||||
sendBefore: () => {
|
||||
scrollBottom()
|
||||
scrollBottom();
|
||||
|
||||
},
|
||||
})
|
||||
.catch(() => {
|
||||
@ -483,7 +643,10 @@ const handleSendTextMsg = () => {
|
||||
inputText.value = ''
|
||||
isReplyMsg.value = false
|
||||
replyMsg.value = undefined
|
||||
selectedAtMembers.value = []
|
||||
selectedAtMembers.value = [];
|
||||
if(type=='mall'){
|
||||
senCustomMsg('mall');
|
||||
}
|
||||
}
|
||||
|
||||
/** 发送文件消息 */
|
||||
@ -629,7 +792,75 @@ const handleSendImageMsg = () => {
|
||||
complete: handleNoPermission,
|
||||
})
|
||||
}
|
||||
const senCustomMsg = (type:string) => {
|
||||
let rawStr="";
|
||||
let text="";
|
||||
if(type == 'hospital'){
|
||||
text="[互联网医院]";
|
||||
rawStr=`{\"gdxz_content\":\"我已入驻肝胆相照互联网医院,复诊购药一站式服务,快来看看吧\",\"gdxz_ext_data\":\"[互联网医院]\",\"gdxz_id\":\"1681174885629431808\",\"gdxz_img\":\"https://img.applets.igandanyiyuan.com/applet/admin/avatar/2023071813261420200708181049.png\",\"gdxz_title\":\"肝胆相照互联网医院\",\"gdxz_type\":\"[互联网医院]\",\"gdxz_url\":\"\"}`;
|
||||
}else if(type == 'mall'){
|
||||
text="[肝胆商城]";
|
||||
rawStr=`{\"gdxz_content\":\"肝胆相照®肝胆病在线公共服务平台\",\"gdxz_ext_data\":\"[肝胆商城]\",\"gdxz_id\":\"\",\"gdxz_img\":\"\",\"gdxz_title\":\"纽娃复合营养素固体饮料\",\"gdxz_type\":\"[肝胆商城]\",\"gdxz_url\":\"https://wx.igandan.com/shop_notify/setInfo?patient_uuid=${patient_uuid}&expert_uuid=${expert_uuid}\"}`;
|
||||
}else if(type == 'outpatient'){
|
||||
text="[门诊公告]";
|
||||
rawStr=`{\"gdxz_content\":\"门诊详情\",\"gdxz_ext_data\":\"[门诊公告]\",\"gdxz_id\":\"\",\"gdxz_img\":\"\",\"gdxz_title\":\"${expert_name}医生门诊详情\",\"gdxz_type\":\"[门诊公告]\",\"gdxz_url\":\"https://dev-wx.igandan.com/wxPatient/index.htm#/outPatient?link=share&expertUuid=${expert_uuid}\"}`
|
||||
}else if(type == 'article'){
|
||||
text="[图文科普]";
|
||||
let content='"'+articleInfo.value.summary+'"';
|
||||
let id='"'+articleInfo.value.uuid+'"';
|
||||
let title='"'+articleInfo.value.title+'"';
|
||||
let path='"'+docUrl+articleInfo.value.path+'"';
|
||||
rawStr=`{\"gdxz_content\":${content},\"gdxz_ext_data\":\"[图文科普]\",\"gdxz_id\":${id},\"gdxz_img\":\"http://doc.igandan.org/app/book/pdf/2019/20190613152617.png\",\"gdxz_title\":${title},\"gdxz_type\":\"[图文科普]\",\"gdxz_url\":${path}}`
|
||||
}else if(type == 'video'){
|
||||
let content='"'+videoInfo.value.note+'"';
|
||||
let id='"'+videoInfo.value.uuid+'"';
|
||||
let title='"'+videoInfo.value.name+'"';
|
||||
let path='"'+docUrl+videoInfo.value.path+'"';
|
||||
text="[视频科普]";
|
||||
rawStr=`{\"gdxz_content\":${content},\"gdxz_ext_data\":\"[视频科普]\",\"gdxz_id\":${id},\"gdxz_img\":\"http://doc.igandan.org/app/book/pdf/2019/20190613152617.png\",\"gdxz_title\":${title},\"gdxz_type\":\"[视频科普]\",\"gdxz_url\":${path}}`
|
||||
}
|
||||
const customMsg = uni.$UIKitNIM.V2NIMMessageCreator.createCustomMessage(text,rawStr)
|
||||
uni.$UIKitStore.msgStore
|
||||
.sendMessageActive({
|
||||
msg: customMsg as unknown as V2NIMMessage,
|
||||
conversationId,
|
||||
progress: () => true,
|
||||
sendBefore: () => {
|
||||
scrollBottom()
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
scrollBottom();
|
||||
})
|
||||
.catch(() => {
|
||||
scrollBottom()
|
||||
uni.showToast({
|
||||
icon: 'error',
|
||||
title: '发送失败',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handleCustom = (type: string, event: any) => {
|
||||
if (isTeamMute.value) return
|
||||
if(type == 'reply'){
|
||||
navTo({
|
||||
url: '/pages_chat/quickReply/quickReply',
|
||||
})
|
||||
}else if(type == 'hj'){
|
||||
showModal.value = true
|
||||
}else if(type == 'mall'){
|
||||
showMallModal.value = true
|
||||
patientListByGBK();
|
||||
}else if(type == 'outpatient'){
|
||||
senCustomMsg('outpatient');
|
||||
// navTo({
|
||||
// url: '/pages_chat/outpatient/outpatient',
|
||||
// })
|
||||
}else if(type == 'hospital'){
|
||||
senCustomMsg('hospital');
|
||||
}
|
||||
}
|
||||
/** 发送视频消息(使用相机或者从相册选择) */
|
||||
const handleSendVideoMsg = (type: string, event: any) => {
|
||||
if (isTeamMute.value) return
|
||||
@ -1073,12 +1304,18 @@ onUnmounted(() => {
|
||||
.send-more-panel-item {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
margin: 0 15px;
|
||||
justify-content: center;
|
||||
image{
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
.icon-text {
|
||||
font-size: 12px;
|
||||
@ -1178,4 +1415,47 @@ onUnmounted(() => {
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 复制自 groupSend 的遮罩与弹窗样式 */
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.center-modal {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 650rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
z-index: 1001;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
text-align: center;
|
||||
font-size: 34rpx;
|
||||
color: #d32f2f;
|
||||
padding: 28rpx 20rpx;
|
||||
}
|
||||
|
||||
.modal-item {
|
||||
padding: 36rpx 28rpx;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.modal-divider {
|
||||
height: 2rpx;
|
||||
background: #eeeeee;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<!-- {{ props.msg }} -->
|
||||
<div
|
||||
:class="`msg-item-wrapper ${
|
||||
props.msg.pinState &&
|
||||
@ -351,6 +352,42 @@
|
||||
</div>
|
||||
<!-- <MessageIsRead v-if="props.msg?.isSelf" :msg="props.msg"></MessageIsRead> -->
|
||||
</div>
|
||||
|
||||
<!-- 自定义消息-->
|
||||
<div
|
||||
class="msg-common"
|
||||
v-else-if="
|
||||
props.msg.messageType ===
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM
|
||||
"
|
||||
:style="{ flexDirection: !props.msg.isSelf ? 'row' : 'row-reverse' }"
|
||||
>
|
||||
<Avatar
|
||||
:account="props.msg.senderId"
|
||||
:teamId="
|
||||
conversationType ===
|
||||
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
||||
? to
|
||||
: ''
|
||||
"
|
||||
:goto-user-card="true"
|
||||
/>
|
||||
<div class="msg-content">
|
||||
<div class="msg-name" v-if="!props.msg.isSelf">
|
||||
{{ appellation }}
|
||||
</div>
|
||||
<MessageBubble
|
||||
:msg="props.msg"
|
||||
:tooltip-visible="true"
|
||||
:bg-visible="true"
|
||||
>
|
||||
<ReplyMessage v-if="!!replyMsg" :replyMsg="replyMsg"></ReplyMessage>
|
||||
<!-- <MessageText :msg="props.msg"></MessageText> -->
|
||||
<MessageCustom :msg="props.msg"></MessageCustom>
|
||||
</MessageBubble>
|
||||
</div>
|
||||
<MessageIsRead v-if="props.msg?.isSelf" :msg="props.msg"></MessageIsRead>
|
||||
</div>
|
||||
<!-- 通知消息-->
|
||||
<MessageNotification
|
||||
v-else-if="
|
||||
@ -383,7 +420,7 @@
|
||||
:tooltip-visible="true"
|
||||
:bg-visible="true"
|
||||
>
|
||||
[{{ t('unknowMsgText') }}]
|
||||
[{{ t('unknowMsgText')}}]
|
||||
</MessageBubble>
|
||||
</div>
|
||||
</div>
|
||||
@ -435,6 +472,7 @@ import MessageNotification from './message-notification.vue'
|
||||
import MessageG2 from './message-g2.vue'
|
||||
import { customNavigateTo } from '@/utils/im/customNavigate'
|
||||
import MessageIsRead from './message-read.vue'
|
||||
import MessageCustom from './message-custom.vue'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import Appellation from '@/components/Appellation.vue'
|
||||
import { events, MSG_ID_FLAG } from '@/utils/im/constants'
|
||||
|
||||
@ -1,67 +1,207 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 顶部导航 -->
|
||||
<view class="nav">
|
||||
<view class="back" @click="onBack">
|
||||
<uni-icons type="left" color="#8B2316" size="24"></uni-icons>
|
||||
</view>
|
||||
<view class="title">群发消息</view>
|
||||
<view class="right"></view>
|
||||
</view>
|
||||
|
||||
<!-- 时间展示 -->
|
||||
<view class="time-row">{{ currentTime }}</view>
|
||||
|
||||
<!-- 卡片区域 -->
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<text class="label">{{ patientCount }}位患者:</text>
|
||||
<view class="close" @click="onClear">
|
||||
<text>×</text>
|
||||
<navBar title="群发消息" />
|
||||
|
||||
<!-- 下拉刷新和上拉加载容器 -->
|
||||
<scroll-view
|
||||
class="scroll-container"
|
||||
scroll-y
|
||||
refresher-enabled
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="onRefresh"
|
||||
@scrolltolower="onLoadMore"
|
||||
:lower-threshold="100"
|
||||
>
|
||||
<!-- 卡片区域 -->
|
||||
<view class="card-cell" v-if="list.length > 0" v-for="(item, index) in list" :key="item.uuid">
|
||||
<view class="time-row">{{ item.msg_send_date }}</view>
|
||||
<view class="card" >
|
||||
|
||||
<view class="card-body">
|
||||
|
||||
<view class="card-header">
|
||||
<text class="label">{{ item.realname.split(',').length }}位患者:</text>
|
||||
<view class="close" @click="onClear">
|
||||
<text>×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="line" >
|
||||
{{ item.realname }}
|
||||
</view>
|
||||
<view class="line phone" >
|
||||
{{ item.msg_content }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<view class="btn-outline" @click="onResend">再发一条</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="line">{{ patientName }}</view>
|
||||
<view class="line phone">{{ patientPhone }}</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="card" v-else>
|
||||
<view class="card-header">
|
||||
<text class="label">群发消息记录</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="line">暂无群发消息记录</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<view class="btn-outline" @click="onResend">再发一条</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 占位滚动区域 -->
|
||||
<view class="spacer" />
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据 -->
|
||||
<view class="no-more" v-if="!hasMore && list.length > 0">
|
||||
<text>没有更多数据了</text>
|
||||
</view>
|
||||
|
||||
<!-- 占位滚动区域 -->
|
||||
<view class="spacer" />
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="btn-primary" @click="onSendGroup">群发消息</view>
|
||||
<view class="btn-primary" @click="openModal">群发消息</view>
|
||||
</view>
|
||||
<!-- 弹窗与遮罩 -->
|
||||
<view v-if="showModal" class="mask" @click="closeModal"></view>
|
||||
<view v-if="showModal" class="center-modal">
|
||||
<view class="modal-title">温馨提示</view>
|
||||
<view class="modal-divider"></view>
|
||||
<view class="modal-item" @click="onSelect('single')">
|
||||
<text>单独选择</text>
|
||||
</view>
|
||||
<view class="modal-divider"></view>
|
||||
<view class="modal-item" @click="onSelect('group')">
|
||||
<text>分组选择</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
import navBar from '@/components/navBar/navBar.vue'
|
||||
import api from '@/api/api.js'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
const currentTime = ref('')
|
||||
const patientCount = ref(1)
|
||||
const patientName = ref('测试')
|
||||
const patientPhone = ref('155555555')
|
||||
|
||||
const showModal=ref(false);
|
||||
const page=ref(1);
|
||||
const list=ref([]);
|
||||
const refreshing=ref(false);
|
||||
const loading=ref(false);
|
||||
const hasMore=ref(true);
|
||||
const pad = (n) => (n < 10 ? `0${n}` : `${n}`)
|
||||
const updateTime = () => {
|
||||
const d = new Date()
|
||||
currentTime.value = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
||||
|
||||
// 格式化时间显示
|
||||
const formatTime = (dateStr) => {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
const now = new Date()
|
||||
const diff = now - date
|
||||
|
||||
// 如果是今天
|
||||
if (diff < 24 * 60 * 60 * 1000 && date.getDate() === now.getDate()) {
|
||||
return `${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||
}
|
||||
// 如果是昨天
|
||||
else if (diff < 48 * 60 * 60 * 1000 && date.getDate() === now.getDate() - 1) {
|
||||
return `昨天 ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||
}
|
||||
// 其他情况显示完整日期
|
||||
else {
|
||||
return `${date.getMonth() + 1}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTime()
|
||||
// 每秒刷新一次时间(如不需要可删除)
|
||||
setInterval(updateTime, 1000)
|
||||
})
|
||||
// 加载数据
|
||||
const listGroupSendMsg=async(isRefresh = false)=>{
|
||||
if(loading.value) return;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const res=await api.listGroupSendMsg({
|
||||
page: page.value,
|
||||
});
|
||||
|
||||
if(res.code==200){
|
||||
const newList = res.data.list || [];
|
||||
|
||||
if(isRefresh) {
|
||||
// 下拉刷新,替换数据
|
||||
list.value = newList;
|
||||
} else {
|
||||
// 上拉加载,追加数据
|
||||
list.value = [...list.value, ...newList];
|
||||
}
|
||||
|
||||
// 判断是否还有更多数据
|
||||
hasMore.value = newList.length >= 10; // 假设每页10条数据
|
||||
|
||||
// 如果不是刷新,页码+1
|
||||
if(!isRefresh) {
|
||||
page.value++;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
uni.showToast({ title: '加载失败', icon: 'none' });
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 下拉刷新
|
||||
const onRefresh = async () => {
|
||||
refreshing.value = true;
|
||||
page.value = 1;
|
||||
hasMore.value = true;
|
||||
|
||||
await listGroupSendMsg(true);
|
||||
|
||||
refreshing.value = false;
|
||||
uni.showToast({ title: '刷新成功', icon: 'success' });
|
||||
};
|
||||
|
||||
// 上拉加载更多
|
||||
const onLoadMore = async () => {
|
||||
if(!hasMore.value || loading.value) return;
|
||||
|
||||
await listGroupSendMsg(false);
|
||||
};
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
onShow(() => {
|
||||
listGroupSendMsg()
|
||||
})
|
||||
const onBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
const openModal = () => {
|
||||
showModal.value = true;
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
const onSelect = (type) => {
|
||||
showModal.value = false;
|
||||
if (type === 'single') {
|
||||
uni.showToast({ title: '单独选择', icon: 'none' });
|
||||
} else if (type === 'group') {
|
||||
uni.showToast({ title: '分组选择', icon: 'none' });
|
||||
}
|
||||
};
|
||||
const onClear = () => {
|
||||
// 清空选择的患者(占位)
|
||||
patientCount.value = 0
|
||||
@ -82,7 +222,48 @@ $brand: #8B2316;
|
||||
$brand-deep: #8B2316;
|
||||
$primary: #00cbc0;
|
||||
$red: #D32F2F;
|
||||
/* 遮罩与弹窗 */
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.6);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.center-modal {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 650rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
z-index: 11;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10rpx 40rpx rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
text-align: center;
|
||||
font-size: 34rpx;
|
||||
color: #8B2316;
|
||||
padding: 28rpx 20rpx;
|
||||
}
|
||||
|
||||
.modal-item {
|
||||
padding: 36rpx 28rpx;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.modal-divider {
|
||||
height: 2rpx;
|
||||
background: #eeeeee;
|
||||
}
|
||||
.page {
|
||||
background: $page-bg;
|
||||
min-height: 100vh;
|
||||
@ -101,16 +282,45 @@ $red: #D32F2F;
|
||||
.right { width: 80rpx; }
|
||||
}
|
||||
|
||||
// 滚动容器样式
|
||||
.scroll-container {
|
||||
position: fixed;
|
||||
top: 140rpx;
|
||||
|
||||
width:100%;
|
||||
bottom: 200rpx;
|
||||
|
||||
}
|
||||
|
||||
.time-row {
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
padding: 32rpx 0;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
// 加载状态样式
|
||||
.loading-state {
|
||||
text-align: center;
|
||||
padding: 40rpx 0;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 40rpx 0;
|
||||
color: #ccc;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.card-cell{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.card {
|
||||
margin: 24rpx;
|
||||
margin: 24rpx 0;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06);
|
||||
@ -122,8 +332,8 @@ $red: #D32F2F;
|
||||
.label { font-size: 32rpx; color: #333; }
|
||||
.close {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
right: -15rpx;
|
||||
top: -18rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-bottom-left-radius: 100rpx;
|
||||
|
||||
347
pages_chat/groupSend/groupSend.vue
Normal file
347
pages_chat/groupSend/groupSend.vue
Normal file
@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 顶部导航 -->
|
||||
<uni-nav-bar
|
||||
left-icon="left"
|
||||
title="群发消息"
|
||||
@clickLeft="goBack"
|
||||
fixed
|
||||
color="#8B2316"
|
||||
height="140rpx"
|
||||
:border="false"
|
||||
backgroundColor="#eee"
|
||||
>
|
||||
<template #right>
|
||||
<view class="nav-right">
|
||||
<uni-icons type="plus" size="24" color="#8B2316" @click="toggleModal"></uni-icons>
|
||||
</view>
|
||||
</template>
|
||||
</uni-nav-bar>
|
||||
|
||||
<!-- 警告提示区域 -->
|
||||
<view class="warning-section">
|
||||
<view class="warning-icon">🔔</view>
|
||||
<text class="warning-text"
|
||||
>若群发消息选择患者过多(建议少于200人), 部分患者可能将延迟收到消息</text
|
||||
>
|
||||
</view>
|
||||
|
||||
<!-- 消息预览卡片 -->
|
||||
<view class="message-preview-card" v-if="selectedPatients.length > 0">
|
||||
<view class="card-header">
|
||||
<text class="label"
|
||||
>消息将发送给{{ selectedPatients.length }}位患者:</text
|
||||
>
|
||||
<view class="close" @click="onClear">
|
||||
<text>×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="patient-list">
|
||||
<text
|
||||
class="patient-name"
|
||||
v-for="(patient, index) in selectedPatients"
|
||||
:key="patient.uuid"
|
||||
>
|
||||
{{ patient.nickname || patient.realName
|
||||
}}{{ index < selectedPatients.length - 1 ? "、" : "" }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态区域 -->
|
||||
<view class="empty-area" v-else>
|
||||
<view class="empty-content">
|
||||
<text class="empty-text">请选择要发送消息的患者</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="bottom-bar">
|
||||
<view style="height: 'auto'">
|
||||
<!-- <MessageInput
|
||||
:reply-msgs-map="replyMsgsMap"
|
||||
:conversation-type="conversationType"
|
||||
:to="to"
|
||||
/> -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 弹窗与遮罩 -->
|
||||
<view v-if="showModal" class="mask" @click="closeModal"></view>
|
||||
<view v-if="showModal" class="center-modal">
|
||||
<view class="modal-title">温馨提示</view>
|
||||
<view class="modal-divider"></view>
|
||||
<view class="modal-item" @click="onSelect('single')">
|
||||
<text>单独选择</text>
|
||||
</view>
|
||||
<view class="modal-divider"></view>
|
||||
<view class="modal-item" @click="onSelect('group')">
|
||||
<text>分组选择</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import navBar from "@/components/navBar/navBar.vue";
|
||||
import api from "@/api/api.js";
|
||||
import { onShow,onLoad,onUnload } from "@dcloudio/uni-app";
|
||||
import navTo from "@/utils/navTo.js";
|
||||
const showModal = ref(false);
|
||||
const selectedPatients = ref([]);
|
||||
const replyMsgsMap = ref({});
|
||||
const conversationType = ref("1");
|
||||
const msg_content = ref("");
|
||||
const msg_type = ref("1");
|
||||
const to = ref("");
|
||||
const toggleModal = () => {
|
||||
showModal.value = !showModal.value;
|
||||
}
|
||||
const goBack = () => {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail: (err) => {
|
||||
navTo({
|
||||
url: "/pages/index/index"
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
const addGroupSendMsg4YunXin=async()=>{
|
||||
const res=await api.addGroupSendMsg4YunXin({
|
||||
patient_user_uuid: selectedPatients.value.map(item => item.uuid).join(','),
|
||||
msg_content: msg_content.value,
|
||||
msg_type: msg_type.value,
|
||||
});
|
||||
if(res.code==200){
|
||||
uni.showToast({ title: "发送成功", icon: "none" });
|
||||
navTo({
|
||||
url: "/pages_chat/groupMessage/groupMessage"
|
||||
})
|
||||
}else{
|
||||
uni.showToast({ title: res.msg, icon: "none" });
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
// 初始化数据
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
try {
|
||||
uni.$on('selectedChatPatients',(data)=>{
|
||||
console.log(data)
|
||||
selectedPatients.value = data.patients;
|
||||
});
|
||||
uni.$on('selectedChatPatientsSingle',(data)=>{
|
||||
console.log(data)
|
||||
selectedPatients.value = data.patients;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('接收已选患者失败', e);
|
||||
}
|
||||
});
|
||||
onUnload(() => {
|
||||
uni.$off('selectedChatPatients');
|
||||
uni.$off('selectedChatPatientsSingle');
|
||||
});
|
||||
const openModal = () => {
|
||||
showModal.value = true;
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
const onSelect = (type) => {
|
||||
showModal.value = false;
|
||||
if (type === "single") {
|
||||
navTo({
|
||||
url: "/pages_app/selectPatient/selectPatient"
|
||||
})
|
||||
// 这里可以跳转到患者选择页面
|
||||
} else if (type === "group") {
|
||||
navTo({
|
||||
url: "/pages_chat/patientGroup/patientGroup"
|
||||
})
|
||||
// 这里可以跳转到分组选择页面
|
||||
}
|
||||
};
|
||||
|
||||
const onClear = () => {
|
||||
selectedPatients.value = [];
|
||||
uni.showToast({ title: "已清空选择", icon: "success" });
|
||||
try { uni.removeStorageSync('GROUP_SEND_SELECTED_PATIENTS'); } catch (e) {}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$page-bg: #f5f5f5;
|
||||
$brand: #8b2316;
|
||||
$brand-deep: #8b2316;
|
||||
$primary: #00cbc0;
|
||||
$red: #d32f2f;
|
||||
|
||||
.page {
|
||||
background: $page-bg;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// 警告提示区域
|
||||
.warning-section {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 24rpx 30rpx;
|
||||
background: #fff8e1;
|
||||
border-left: 6rpx solid #ff9800;
|
||||
margin: 20rpx 30rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.warning-icon {
|
||||
font-size: 28rpx;
|
||||
margin-right: 16rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
font-size: 26rpx;
|
||||
color: #e65100;
|
||||
line-height: 1.5;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 消息预览卡片
|
||||
.message-preview-card {
|
||||
margin: 30rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
border-top: 6rpx solid $red;
|
||||
|
||||
.card-header {
|
||||
position: relative;
|
||||
padding: 28rpx 28rpx 12rpx 28rpx;
|
||||
|
||||
.label {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
right: -15rpx;
|
||||
top: -18rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-bottom-left-radius: 100rpx;
|
||||
background: $red;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
text {
|
||||
color: #fff;
|
||||
font-size: 40rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20rpx 28rpx 28rpx 28rpx;
|
||||
|
||||
.patient-list {
|
||||
.patient-name {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 空状态区域
|
||||
.empty-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 30rpx;
|
||||
|
||||
.empty-content {
|
||||
text-align: center;
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部按钮
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: $primary;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
// 遮罩与弹窗
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.center-modal {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 650rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
z-index: 11;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
text-align: center;
|
||||
font-size: 34rpx;
|
||||
color: $red;
|
||||
padding: 28rpx 20rpx;
|
||||
}
|
||||
|
||||
.modal-item {
|
||||
padding: 36rpx 28rpx;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.modal-divider {
|
||||
height: 2rpx;
|
||||
background: #eeeeee;
|
||||
}
|
||||
</style>
|
||||
714
pages_chat/patientGroup/patientGroup.vue
Normal file
714
pages_chat/patientGroup/patientGroup.vue
Normal file
@ -0,0 +1,714 @@
|
||||
<template>
|
||||
<view class="patient-group-page">
|
||||
|
||||
<!-- 头部 -->
|
||||
<uni-nav-bar
|
||||
left-icon="left"
|
||||
title="患者分组"
|
||||
@clickLeft="goBack"
|
||||
fixed
|
||||
color="#8B2316"
|
||||
height="140rpx"
|
||||
:border="false"
|
||||
backgroundColor="#eeeeee"
|
||||
>
|
||||
<template #right>
|
||||
<view class="save-text" @click="confirmGroup">确定({{ totalSelectedCount }})</view>
|
||||
</template>
|
||||
</uni-nav-bar>
|
||||
<!-- 筛选排序栏 -->
|
||||
<view class="filter-sort-bar">
|
||||
<view class="sort-section" @click="toggleGroupSort">
|
||||
<text class="sort-label">{{ groupSortTitle }}</text>
|
||||
<view class="imgbox">
|
||||
<up-image :src="upImg" width="26rpx" height="26rpx" ></up-image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="divider"></view>
|
||||
<view class="current-sort" @click="toggleInnerSort">
|
||||
<text class="sort-text">{{ innerSortTitle }}</text>
|
||||
<view class="imgbox">
|
||||
<up-image :src="upImg" width="26rpx" height="26rpx" ></up-image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分组排序弹窗 -->
|
||||
<view v-if="showGroupSort" class="popup-panel">
|
||||
<view class="popup-item" :class="{ active: group_sort==0 }" @click.stop="chooseGroupSort('letter')">
|
||||
<text class="item-text">按首字母</text>
|
||||
<uni-icons v-if="selectedGroupSort==='letter'" type="checkmarkempty" color="#8B2316" size="22"></uni-icons>
|
||||
</view>
|
||||
<view class="popup-divider"></view>
|
||||
<view class="popup-item" :class="{ active:group_sort==1}" @click.stop="chooseGroupSort('count')">
|
||||
<text class="item-text">分组人数</text>
|
||||
<uni-icons v-if="selectedGroupSort==='count'" type="checkmarkempty" color="#8B2316" size="22"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="showGroupSort" class="popup-mask" @click="closeGroupSort"></view>
|
||||
|
||||
<!-- 组内排序弹窗 -->
|
||||
<view v-if="showInnerSort" class="popup-panel">
|
||||
<view class="popup-item" :class="{ active:list_sort==0 }" @click.stop="chooseInnerSort('letter')">
|
||||
<text class="item-text">按首字母</text>
|
||||
<uni-icons v-if="selectedInnerSort==='letter'" type="checkmarkempty" color="#8B2316" size="22"></uni-icons>
|
||||
</view>
|
||||
<view class="popup-divider"></view>
|
||||
<view class="popup-item" :class="{ active:list_sort==1 }" @click.stop="chooseInnerSort('count')">
|
||||
<text class="item-text">随访时间</text>
|
||||
<uni-icons v-if="selectedInnerSort==='count'" type="checkmarkempty" color="#8B2316" size="22"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="showInnerSort" class="popup-mask" @click="closeInnerSort"></view>
|
||||
|
||||
<!-- 患者列表 -->
|
||||
<scroll-view class="patient-list-section" scroll-y="true" :style="{ height: scrollViewHeight }">
|
||||
<!-- 分组循环渲染 -->
|
||||
<view class="groupcell" v-for="(group, gi) in groups" :key="group.uuid || gi">
|
||||
<view class="section-title" @click="toggleGroup(gi)">
|
||||
<view class="left">
|
||||
<view class="imgbox" :class="{ 'active':openGroups[gi] }">
|
||||
<up-image v-if="!openGroups[gi]" :src="groupRightImg" width="19rpx" height="32rpx" ></up-image>
|
||||
<up-image v-else :src="groupDownImg" width="32rpx" height="19rpx" ></up-image>
|
||||
</view>
|
||||
<view class="title">{{ group.name || '未命名分组' }} | {{ group.patientNum || (group.patientList ? group.patientList.length : 0) }}</view>
|
||||
</view>
|
||||
<view class="right-checkbox" @click.stop="toggleGroupSelection(gi)" v-if="group.patientList.length > 0">
|
||||
<view class="checkbox" :class="{ 'checked': isGroupSelected(gi) }">
|
||||
<uni-icons v-if="isGroupSelected(gi)" type="checkmarkempty" color="#8B2316" size="16"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<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-avatar">
|
||||
<up-image :src="docUrl + patient.photo" width="80rpx" height="80rpx" mode="aspectFill"></up-image>
|
||||
</view>
|
||||
<view class="patient-checkbox" @click.stop="togglePatientSelection(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>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="clear-btn" @click="clearSelection">清空选择</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { onShow,onLoad } from "@dcloudio/uni-app";
|
||||
import upImg from "@/static/triangle_green_theme.png"
|
||||
import downImg from "@/static/triangle_normal.png"
|
||||
import groupRightImg from "@/static/groupright_big.png"
|
||||
import groupDownImg from "@/static/groupup_big.png"
|
||||
import api from '@/api/api.js';
|
||||
import docUrl from '@/utils/docUrl'
|
||||
import dayjs from 'dayjs'
|
||||
import navTo from '@/utils/navTo.js'
|
||||
const list_sort = ref(0);
|
||||
const group_sort = ref(0);
|
||||
|
||||
// 分组数据与展开状态
|
||||
const groups = ref([]);
|
||||
const openGroups = ref({});
|
||||
// 选中的分组状态
|
||||
const selectedGroups = ref({});
|
||||
// 选中的患者状态:以分组索引为key,值为Set或索引Map
|
||||
const selectedPatients = ref({});
|
||||
|
||||
const formatYMD = (val) => {
|
||||
if (!val) return '';
|
||||
const d = dayjs(val);
|
||||
return d.isValid() ? d.format('YYYY-MM-DD') : '';
|
||||
};
|
||||
|
||||
// 排序栏标题
|
||||
const groupSortTitle = computed(() => group_sort.value === 0 ? '按首字母' : '分组人数');
|
||||
const innerSortTitle = computed(() => list_sort.value === 0 ? '按首字母' : '随访时间');
|
||||
|
||||
// 计算选中的分组数量
|
||||
const selectedGroupCount = computed(() => {
|
||||
return Object.values(selectedGroups.value).filter(Boolean).length;
|
||||
});
|
||||
|
||||
// 计算选中的患者数量(去重)
|
||||
const getPatientUniqueId = (p) => {
|
||||
return (p && (p.uuid || p.patientUuid || p.id || p.userId)) || '';
|
||||
};
|
||||
|
||||
const selectedPatientCount = computed(() => {
|
||||
const idSet = new Set();
|
||||
groups.value.forEach((group, gi) => {
|
||||
const map = selectedPatients.value[gi] || {};
|
||||
(group.patientList || []).forEach((patient, pi) => {
|
||||
if (map[pi]) {
|
||||
const uid = getPatientUniqueId(patient);
|
||||
if (uid) idSet.add(uid);
|
||||
}
|
||||
});
|
||||
});
|
||||
return idSet.size;
|
||||
});
|
||||
|
||||
// 合计数量:只计算去重后的患者数量
|
||||
const totalSelectedCount = computed(() => selectedPatientCount.value);
|
||||
|
||||
onShow(() => {
|
||||
fetchGroupList();
|
||||
});
|
||||
|
||||
const fetchGroupList = async () => {
|
||||
const res = await api.groupList({
|
||||
list_sort:list_sort.value,
|
||||
page:1,
|
||||
pageSize:10,
|
||||
group_sort:group_sort.value
|
||||
});
|
||||
if(res.code === 200){
|
||||
groups.value = Array.isArray(res.data) ? res.data : [];
|
||||
// 初始化展开状态:默认收起
|
||||
openGroups.value = {};
|
||||
groups.value.forEach((group, idx) => {
|
||||
openGroups.value[idx] = false;
|
||||
// 初始化患者选择Map,避免未定义
|
||||
if (!selectedPatients.value[idx]) selectedPatients.value[idx] = {};
|
||||
// 若组选中但患者为空,则保持组选中;否则按患者是否全选同步组选中状态
|
||||
updateGroupSelectionByPatients(idx);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const toggleGroup = (idx) => {
|
||||
openGroups.value[idx] = !openGroups.value[idx];
|
||||
};
|
||||
|
||||
const toggleGroupSelection = (idx) => {
|
||||
const group = groups.value[idx] || {};
|
||||
const patients = group.patientList || [];
|
||||
const willSelect = !isGroupSelected(idx);
|
||||
// 设置组选中状态
|
||||
selectedGroups.value[idx] = willSelect;
|
||||
// 同步该组下的患者选中状态
|
||||
if (!selectedPatients.value[idx]) selectedPatients.value[idx] = {};
|
||||
patients.forEach((_, pi) => {
|
||||
selectedPatients.value[idx][pi] = willSelect;
|
||||
});
|
||||
};
|
||||
|
||||
const isPatientSelected = (gi, patient, index) => {
|
||||
const groupSelectedMap = selectedPatients.value[gi] || {};
|
||||
return !!groupSelectedMap[index];
|
||||
};
|
||||
|
||||
const togglePatientSelection = (gi, patient, index) => {
|
||||
if (!selectedPatients.value[gi]) selectedPatients.value[gi] = {};
|
||||
selectedPatients.value[gi][index] = !selectedPatients.value[gi][index];
|
||||
// 若组内所有患者都被勾选,则自动勾选组;否则取消组勾选
|
||||
updateGroupSelectionByPatients(gi);
|
||||
};
|
||||
|
||||
const isGroupSelected = (gi) => {
|
||||
// 优先:若显式设置了组选中状态,且为true,直接认为选中
|
||||
if (selectedGroups.value[gi]) return true;
|
||||
// 否则根据患者是否全选判断
|
||||
const group = groups.value[gi] || {};
|
||||
const patients = group.patientList || [];
|
||||
if (patients.length === 0) return !!selectedGroups.value[gi];
|
||||
const map = selectedPatients.value[gi] || {};
|
||||
return patients.every((_, pi) => !!map[pi]);
|
||||
};
|
||||
|
||||
const updateGroupSelectionByPatients = (gi) => {
|
||||
const group = groups.value[gi] || {};
|
||||
const patients = group.patientList || [];
|
||||
const map = selectedPatients.value[gi] || {};
|
||||
if (patients.length === 0) return;
|
||||
selectedGroups.value[gi] = patients.every((_, pi) => !!map[pi]);
|
||||
};
|
||||
|
||||
const editGroup = (group) => {
|
||||
navTo({
|
||||
url: `/pages_app/groupEdit/groupEdit?uuid=${group.uuid}`
|
||||
})
|
||||
};
|
||||
|
||||
// 分组排序弹窗状态
|
||||
const showGroupSort = ref(false);
|
||||
const selectedGroupSort = ref('letter'); // letter | count
|
||||
|
||||
// 组内排序弹窗状态
|
||||
const showInnerSort = ref(false);
|
||||
const selectedInnerSort = ref('letter');
|
||||
|
||||
// 待分组展开/收起
|
||||
const showPending = ref(false);
|
||||
|
||||
const toggleGroupSort = () => {
|
||||
showGroupSort.value = !showGroupSort.value;
|
||||
if (showGroupSort.value) showInnerSort.value = false;
|
||||
};
|
||||
const closeGroupSort = () => {
|
||||
showGroupSort.value = false;
|
||||
};
|
||||
|
||||
const chooseGroupSort = (type) => {
|
||||
selectedGroupSort.value = type;
|
||||
// 修改接口字段:按首字母=0,分组人数=1
|
||||
group_sort.value = type === 'letter' ? 0 : 1;
|
||||
closeGroupSort();
|
||||
fetchGroupList();
|
||||
};
|
||||
const confirmGroup = () => {
|
||||
// 收集已选患者,并做去重
|
||||
const idSet = new Set();
|
||||
const dedupPatients = [];
|
||||
groups.value.forEach((group, gi) => {
|
||||
const map = selectedPatients.value[gi] || {};
|
||||
(group.patientList || []).forEach((patient, pi) => {
|
||||
if (map[pi]) {
|
||||
const uid = getPatientUniqueId(patient);
|
||||
if (uid && !idSet.has(uid)) {
|
||||
idSet.add(uid);
|
||||
dedupPatients.push(patient);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (dedupPatients.length === 0) {
|
||||
uni.showToast({ title: '请至少选择1个患者', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
uni.navigateTo({
|
||||
url: '/pages_chat/groupSend/groupSend',
|
||||
success: (res) => {
|
||||
try {
|
||||
uni.$emit('selectedChatPatients', { patients: dedupPatients });
|
||||
} catch (emitErr) {
|
||||
console.error('传递已选患者失败', emitErr);
|
||||
uni.showToast({ title: '传递失败,请重试', icon: 'none' });
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('跳转失败', e);
|
||||
uni.showToast({ title: '跳转失败,请重试', icon: 'none' });
|
||||
}
|
||||
}
|
||||
const toggleInnerSort = () => {
|
||||
showInnerSort.value = !showInnerSort.value;
|
||||
if (showInnerSort.value) showGroupSort.value = false;
|
||||
};
|
||||
|
||||
const closeInnerSort = () => {
|
||||
showInnerSort.value = false;
|
||||
};
|
||||
|
||||
const chooseInnerSort = (type) => {
|
||||
selectedInnerSort.value = type;
|
||||
// 组内排序:按首字母=0,随访时间=1
|
||||
list_sort.value = type === 'letter' ? 0 : 1;
|
||||
closeInnerSort();
|
||||
fetchGroupList();
|
||||
};
|
||||
|
||||
// 方法
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const createNew = () => {
|
||||
uni.showToast({
|
||||
title: '跳转到新建分组页面',
|
||||
icon: 'none'
|
||||
});
|
||||
// 这里可以跳转到新建分组页面
|
||||
};
|
||||
|
||||
// 清空选择
|
||||
const clearSelection = () => {
|
||||
selectedGroups.value = {};
|
||||
selectedPatients.value = {};
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.patient-group-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.popup-mask {
|
||||
position: fixed;
|
||||
top: 220rpx; /* 位于筛选栏下方 */
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.3);
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
.popup-panel {
|
||||
position: fixed;
|
||||
top: 220rpx; /* 紧贴筛选栏 */
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
z-index: 10;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.popup-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28rpx 30rpx;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.popup-divider {
|
||||
height: 2rpx;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
.popup-item.active .item-text {
|
||||
color: #8B2316;
|
||||
}
|
||||
.save-text{
|
||||
margin-top: -20rpx;
|
||||
color:#8B2316;
|
||||
font-size: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
.status-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10rpx 30rpx;
|
||||
background-color: #ffffff;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
|
||||
.status-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
|
||||
.time {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.app-icons {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
|
||||
.app-icon {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
border-radius: 4rpx;
|
||||
|
||||
&.weibo {
|
||||
background-color: #ff8200;
|
||||
}
|
||||
|
||||
&.taobao {
|
||||
background-color: #ff6a00;
|
||||
}
|
||||
|
||||
&.xiaohongshu {
|
||||
background-color: #ff2442;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
|
||||
.network {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.signal-icon {
|
||||
width: 32rpx;
|
||||
height: 20rpx;
|
||||
background-color: #333;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
|
||||
.battery-text {
|
||||
font-size: 20rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.battery {
|
||||
width: 40rpx;
|
||||
height: 20rpx;
|
||||
border: 2rpx solid #333;
|
||||
border-radius: 4rpx;
|
||||
background-color: #4caf50;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -6rpx;
|
||||
top: 6rpx;
|
||||
width: 4rpx;
|
||||
height: 8rpx;
|
||||
background-color: #333;
|
||||
border-radius: 0 2rpx 2rpx 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.back-btn {
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 36rpx;
|
||||
font-weight: normal;
|
||||
color: #ff0000;
|
||||
margin-right: 60rpx; // 为了保持标题居中
|
||||
}
|
||||
|
||||
.new-btn {
|
||||
padding: 10rpx;
|
||||
|
||||
.new-text {
|
||||
font-size: 28rpx;
|
||||
color: #ff0000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-sort-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content:center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #ffff;
|
||||
position: fixed;
|
||||
top: 140rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
.imgbox{
|
||||
width: 32rpx;
|
||||
margin-top: -10rpx;
|
||||
}
|
||||
.sort-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
|
||||
.sort-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.sort-icon.down {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 8rpx solid transparent;
|
||||
border-right: 8rpx solid transparent;
|
||||
border-top: 12rpx solid #999;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 2rpx;
|
||||
height: 40rpx;
|
||||
background-color: #e0e0e0;
|
||||
margin: 0 80rpx;
|
||||
}
|
||||
|
||||
.current-sort {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
|
||||
.sort-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.sort-icon.up {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 8rpx solid transparent;
|
||||
border-right: 8rpx solid transparent;
|
||||
border-bottom: 12rpx solid #999;
|
||||
|
||||
&.active {
|
||||
border-bottom-color: #ff0000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.patient-list-section {
|
||||
top: 240rpx;
|
||||
width:100%;
|
||||
bottom:120rpx; // 预留底部操作栏高度
|
||||
position: fixed;
|
||||
background-color: #ffffff;
|
||||
|
||||
.section-title {
|
||||
padding: 30rpx 30rpx 20rpx;
|
||||
font-size: 32rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-weight: normal;
|
||||
display: flex;
|
||||
color: #333;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.left{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
}
|
||||
.imgbox{
|
||||
&.active{
|
||||
margin-top: -20rpx;
|
||||
}
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
.right-checkbox{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
.checkbox{
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 2rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.checked{
|
||||
border-color: #8B2316;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.patient-list {
|
||||
// 滚动视图样式
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.patient-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
padding: 30rpx 40rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.patient-checkbox{
|
||||
margin-right: 8rpx;
|
||||
.checkbox{
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 2rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
transition: all 0.3s ease;
|
||||
&.checked{ border-color: #8B2316; }
|
||||
}
|
||||
}
|
||||
|
||||
.patient-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.patient-info {
|
||||
flex: 1;
|
||||
|
||||
.patient-name {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.follow-up-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-bar{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
box-shadow: 0 -6rpx 20rpx rgba(0,0,0,0.06);
|
||||
padding: 20rpx 30rpx env(safe-area-inset-bottom);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
.align-right{justify-content:flex-end;}
|
||||
|
||||
.clear-btn{
|
||||
width: 100%;
|
||||
margin-bottom: 20rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
border-radius: 12rpx;
|
||||
border: 2rpx solid #8B2316;
|
||||
color: #8B2316;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
279
pages_chat/quickReply/quickReply.vue
Normal file
279
pages_chat/quickReply/quickReply.vue
Normal file
@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<view class="quick-reply-page">
|
||||
<uni-nav-bar
|
||||
left-icon="left"
|
||||
@clickLeft="goBack"
|
||||
title="快捷回复"
|
||||
fixed
|
||||
color="#8B2316"
|
||||
height="140rpx"
|
||||
:border="false"
|
||||
backgroundColor="#f5f5f5"
|
||||
/>
|
||||
|
||||
<!-- 顶部添加快捷回复入口 -->
|
||||
<view class="add-entry" @click="onAdd">
|
||||
<view class="row">
|
||||
<view class="add-icon">
|
||||
<uni-icons type="plusempty" color="#8B2316" size="22" />
|
||||
</view>
|
||||
<text class="add-text">添加快捷回复</text>
|
||||
</view>
|
||||
<view class="bar">
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 列表 -->
|
||||
<scroll-view class="list" scroll-y>
|
||||
<view
|
||||
class="item"
|
||||
v-for="(text, index) in replies"
|
||||
:key="index"
|
||||
@click="onSelect(text.replystr)"
|
||||
@longpress="onLongPress(text.uuid)"
|
||||
>
|
||||
<text class="item-text">{{text.replystr }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 输入内容弹窗 -->
|
||||
<UniPopup
|
||||
ref="popupRef"
|
||||
type="center"
|
||||
background-color="#ffffff"
|
||||
mask-background-color="rgba(0,0,0,0.4)"
|
||||
>
|
||||
<view class="input-popup">
|
||||
<view class="popup-title">新增快捷回复</view>
|
||||
<textarea
|
||||
v-model="inputContent"
|
||||
class="popup-textarea"
|
||||
:maxlength="-1"
|
||||
placeholder="请输入回复内容"
|
||||
auto-height
|
||||
/>
|
||||
<view class="popup-actions">
|
||||
<view class="btn cancel" @click="onCancel">取消</view>
|
||||
<view class="btn confirm" @click="onConfirm">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</UniPopup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, getCurrentInstance } from 'vue'
|
||||
// @ts-ignore
|
||||
const delUuid=ref('');
|
||||
import UniPopup from '@/components/uni-components/uni-popup/components/uni-popup/uni-popup.vue'
|
||||
import api from '@/api/api'
|
||||
// 示例数据(后续可替换为接口数据或本地存储)
|
||||
const replies = ref([])
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack({
|
||||
fail() {
|
||||
uni.redirectTo({ url: '/pages/index/index' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const popupRef = ref(null)
|
||||
const inputContent = ref('')
|
||||
|
||||
const onAdd = () => {
|
||||
// @ts-ignore
|
||||
popupRef.value && popupRef.value.open('center')
|
||||
}
|
||||
|
||||
const onSelect = (text) => {
|
||||
uni.$emit('quickReply', text);
|
||||
uni.navigateBack()
|
||||
}
|
||||
const getQuickReplyList = async () => {
|
||||
const res = await api.quickReplyList({})
|
||||
if(res.code==200){
|
||||
replies.value = res.data
|
||||
}
|
||||
}
|
||||
const addQuickReply = async () => {
|
||||
const res = await api.addQuickReply({
|
||||
replystr:inputContent.value
|
||||
})
|
||||
if(res.code==200){
|
||||
uni.showToast({title: '添加成功', icon: 'none'})
|
||||
getQuickReplyList()
|
||||
}
|
||||
}
|
||||
const deleteQuickReply = async () => {
|
||||
const res = await api.deleteQuickReply({
|
||||
uuid:delUuid.value
|
||||
})
|
||||
if(res.code==200){
|
||||
uni.showToast({title: '删除成功', icon: 'none'})
|
||||
getQuickReplyList()
|
||||
}
|
||||
};
|
||||
// 长按删除
|
||||
const onLongPress = (index) => {
|
||||
delUuid.value = index;
|
||||
uni.showActionSheet({
|
||||
itemList: ['删除该条快捷回复'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: '删除后不可恢复,是否删除?',
|
||||
confirmColor: '#8B2316',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
deleteQuickReply()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
// @ts-ignore
|
||||
popupRef.value && popupRef.value.close()
|
||||
inputContent.value = ''
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
const text = inputContent.value.trim()
|
||||
if (!text) {
|
||||
uni.showToast({ title: '请输入内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
replies.value = [text, ...replies.value]
|
||||
uni.setClipboardData({ data: text, showToast: false })
|
||||
uni.showToast({ title: '已添加并复制', icon: 'none' })
|
||||
onCancel()
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bar{
|
||||
width:100%;
|
||||
height:20rpx;
|
||||
background-color: #e3e3e3;
|
||||
}
|
||||
.quick-reply-page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* 顶部添加入口 */
|
||||
.add-entry {
|
||||
|
||||
|
||||
position: fixed;
|
||||
top: 140rpx; /* 与导航栏高度一致 */
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
.row{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 30rpx;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #8b2316;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.add-text {
|
||||
font-size: 30rpx;
|
||||
color: #8b2316;
|
||||
}
|
||||
|
||||
/* 列表区域 */
|
||||
.list {
|
||||
position: fixed;
|
||||
top: 272rpx; /* 导航栏 + 添加入口高度(约) */
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 28rpx 30rpx;
|
||||
border-bottom: 1rpx solid #eeeeee;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
line-height: 1.6;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.input-popup{
|
||||
width: 640rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.popup-title{
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.popup-textarea{
|
||||
width: 100%;
|
||||
min-height: 160rpx;
|
||||
background: #f7f7f7;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
.popup-actions{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
.btn{
|
||||
flex: 1;
|
||||
height: 72rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.btn.cancel{
|
||||
background-color: #ffffff;
|
||||
border: 2rpx solid #8b2316;
|
||||
color: #8b2316;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.btn.confirm{
|
||||
background-color: #8b2316;
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
|
||||
762
pages_chat/searchArticle/searchArticle.vue
Normal file
762
pages_chat/searchArticle/searchArticle.vue
Normal file
@ -0,0 +1,762 @@
|
||||
<template>
|
||||
<view class="search-header">
|
||||
<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="cancel-btn" @click="goSearch">
|
||||
<text class="cancel-text">搜索</text>
|
||||
</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.DES }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="filter-buttons">
|
||||
<view class="btn-reset" @click="resetFilter">重置</view>
|
||||
<view class="btn-confirm" @click="confirmFilter">确定</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="article.image" mode="aspectFill"></image>
|
||||
<view class="article-content">
|
||||
<view class="article-title">{{ article.title }}</view>
|
||||
<view class="article-meta">
|
||||
<view class="date-tag" v-if="article.isToday">今日</view>
|
||||
<view class="date" v-else>{{ article.date }}</view>
|
||||
<view class="stats">
|
||||
<view class="stat-item">
|
||||
<uni-icons type="eye" size="12" color="#999"></uni-icons>
|
||||
<text>{{ article.views }}</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<uni-icons type="hand-up" size="13" color="#999"></uni-icons>
|
||||
<text>{{ article.likes }}</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>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onShow } 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 docUrl from '@/utils/docUrl.js';
|
||||
import navTo from '@/utils/navTo.js';
|
||||
import navBar from '@/components/navBar/navBar.vue';
|
||||
|
||||
const type=ref(1)
|
||||
|
||||
// 分页相关状态
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(10);
|
||||
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 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 polularScienceArticleListByKeywordsNew = async (isRefresh = false) => {
|
||||
if (loading.value) return;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const page = isRefresh ? 1 : currentPage.value;
|
||||
const res = await api.polularScienceArticleListByKeywordsNew({
|
||||
keywords: keywords.value,
|
||||
page: page,
|
||||
type: type.value
|
||||
});
|
||||
|
||||
if (res && res.code === 200 && res.data) {
|
||||
const newData = res.data.list || [];
|
||||
|
||||
if (isRefresh) {
|
||||
// 下拉刷新:替换数据
|
||||
articleList.value = newData.map(item => formatArticleData(item));
|
||||
currentPage.value = 1;
|
||||
} else {
|
||||
// 上拉加载:追加数据
|
||||
const formattedData = newData.map(item => formatArticleData(item));
|
||||
articleList.value = [...articleList.value, ...formattedData];
|
||||
}
|
||||
|
||||
// 判断是否还有更多数据
|
||||
hasMore.value = newData.length >= pageSize.value;
|
||||
|
||||
if (!isRefresh) {
|
||||
currentPage.value++;
|
||||
}
|
||||
|
||||
console.log('文章数据加载成功:', articleList.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取文章列表失败:', error);
|
||||
uni.showToast({
|
||||
title: '获取数据失败',
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
refreshing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化文章数据
|
||||
const formatArticleData = (item) => {
|
||||
// 安全检查,确保item存在且有所需属性
|
||||
if (!item) return null;
|
||||
|
||||
const submitDate = item.submitDate ? new Date(item.submitDate) : new Date();
|
||||
const today = new Date();
|
||||
const isToday = submitDate.toDateString() === today.toDateString();
|
||||
|
||||
return {
|
||||
uuid: item.uuid || '',
|
||||
title: item.topic || '无标题',
|
||||
summary: item.summary || '',
|
||||
image: item.imgPath ? docUrl + item.imgPath : '/static/liver_knowledge.png',
|
||||
date: isToday ? '今日' : formatDate(submitDate),
|
||||
isToday: isToday,
|
||||
views: item.readnum || 0,
|
||||
likes: item.agreenum || 0,
|
||||
tags: item.tags || '',
|
||||
path: item.path || '',
|
||||
submitDate: item.submitDate || ''
|
||||
};
|
||||
};
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (date) => {
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${month}-${day}`;
|
||||
};
|
||||
// 筛选标签数据
|
||||
const filterTags = ref([]);
|
||||
|
||||
// 文章列表数据
|
||||
const articleList = ref([]);
|
||||
|
||||
// 方法
|
||||
const switchTab = (index) => {
|
||||
|
||||
if(index==1){
|
||||
navTo({
|
||||
url: '/pages_app/patientVideo/patientVideo'
|
||||
})
|
||||
}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 = (article) => {
|
||||
console.log('查看文章详情:', article);
|
||||
// 跳转到详情页
|
||||
uni.$emit('articelItem', article);
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
// 跳转到搜索页面
|
||||
const goToSearch = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_app/search/search'
|
||||
});
|
||||
};
|
||||
|
||||
// 下拉刷新
|
||||
const onRefresh = async () => {
|
||||
refreshing.value = true;
|
||||
await polularScienceArticleListByKeywordsNew(true);
|
||||
uni.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success',
|
||||
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 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.DES);
|
||||
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();
|
||||
});
|
||||
</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 - 140rpx);
|
||||
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 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background-color: #eeeeee;
|
||||
|
||||
display: flex;
|
||||
height:140rpx;
|
||||
align-items: center;
|
||||
padding:0 30rpx;
|
||||
gap: 20rpx;
|
||||
|
||||
|
||||
.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;
|
||||
|
||||
.cancel-text {
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 筛选栏
|
||||
.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: 80px;
|
||||
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: $padding-small;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
860
pages_chat/video/video.vue
Normal file
860
pages_chat/video/video.vue
Normal file
@ -0,0 +1,860 @@
|
||||
<template>
|
||||
<uni-nav-bar left-icon="left"
|
||||
@clickLeft="goBack" title="患教视频" fixed color="#8B2316" height="140rpx" :border="false" backgroundColor="#eeeeee"></uni-nav-bar>
|
||||
<view class="video-page">
|
||||
<!-- Header -->
|
||||
<!-- Fixed Filter Tabs -->
|
||||
<view class="filter-tabs-container">
|
||||
<view class="filter-tabs">
|
||||
<view
|
||||
class="tab-item"
|
||||
@click="showAllVideoPopup=!showAllVideoPopup"
|
||||
>
|
||||
<up-image :src="isAllActive?allOnImg:allImg" width="30rpx" height="30rpx" ></up-image>
|
||||
<text class="tab-text" :class="{active:isAllActive}">{{typeName}}</text>
|
||||
<up-image :src="isAllActive?selectOnImg:selectImg" width="30rpx" height="30rpx" ></up-image>
|
||||
</view>
|
||||
<view class="divider"></view>
|
||||
<view class="right">
|
||||
<view
|
||||
class="tab-item"
|
||||
@click="toggleSort"
|
||||
>
|
||||
<text class="tab-text active">{{sort==2?'最新':'最热'}}</text>
|
||||
<view class="newbox">
|
||||
<up-image :src="sort==2?upImg:downImg" width="20rpx" height="26rpx" ></up-image>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view
|
||||
class="tab-item"
|
||||
@click="showFilterPopup"
|
||||
>
|
||||
<text class="tab-text " :class="{active:isFilterActive}">筛选</text>
|
||||
<view class="filterbox">
|
||||
<up-image :src="isFilterActive ? filterOn : filter" width="30rpx" height="30rpx" ></up-image>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Main Content -->
|
||||
<scroll-view
|
||||
class="scroll-view"
|
||||
scroll-y="true"
|
||||
refresher-enabled="true"
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="onRefresh"
|
||||
@scrolltolower="onLoadMore"
|
||||
lower-threshold="100"
|
||||
>
|
||||
|
||||
<!-- Video List -->
|
||||
<view class="video-list">
|
||||
<view
|
||||
class="video-item"
|
||||
v-for="(video, index) in videoList"
|
||||
:key="video.id || video.uuid"
|
||||
@click="playVideo(video)"
|
||||
>
|
||||
<view class="video-thumbnail">
|
||||
<image :src="docUrl + video.imgpath" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="video-info">
|
||||
<view class="video-title">{{video.title || video.name}}</view>
|
||||
<view class="video-meta">
|
||||
<text class="author">{{video.public_name}}</text>
|
||||
<view class="stats">
|
||||
<uni-icons type="eye" size="14" color="#999"></uni-icons>
|
||||
<text class="view-count">{{video.readnum}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Loading More -->
|
||||
<uni-load-more
|
||||
v-if="videoList.length > 0"
|
||||
:status="loadMoreStatus"
|
||||
:content-text="{
|
||||
contentdown: '上拉加载更多',
|
||||
contentrefresh: '正在加载...',
|
||||
contentnomore: '没有更多数据了'
|
||||
}"
|
||||
></uni-load-more>
|
||||
|
||||
<!-- Empty State -->
|
||||
<view class="empty-state" v-if="videoList.length === 0 && !loading">
|
||||
<empty></empty>
|
||||
</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="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>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed,reactive } from 'vue';
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import api from '@/api/api.js';
|
||||
import allImg from "@/static/video_all.png"
|
||||
import allOnImg from "@/static/video_select.png"
|
||||
import selectImg from "@/static/all_video.png"
|
||||
import selectOnImg from "@/static/select_video.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_down.png"
|
||||
import docUrl from '@/utils/docUrl';
|
||||
import navTo from '@/utils/navTo';
|
||||
import empty from '@/components/empty/empty.vue';
|
||||
const isAllActive=ref(false)
|
||||
// 响应式数据
|
||||
const videoList = ref([]);
|
||||
const activeTab = ref(1); // 默认选中"最新"
|
||||
const loading = ref(false);
|
||||
const refreshing = ref(false);
|
||||
const loadMoreStatus = ref('more'); // more, loading, noMore
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const hasMoreData = ref(true);
|
||||
const filteredContent=ref([])
|
||||
const sort =ref(2);
|
||||
const keywords=ref('');
|
||||
const typeUuid=ref('0e5fa3d76b8047528fdd3c452b77e9dd');
|
||||
const typeName=ref('乙肝')
|
||||
const isFilterActive=ref(false)
|
||||
const showFilter = ref(false);
|
||||
const yearList=ref([]);
|
||||
const selectYearContent=reactive({})
|
||||
// 筛选弹窗相关数据
|
||||
const filterTags = ref([]);
|
||||
|
||||
// 全部视频弹窗相关数据
|
||||
const showAllVideoPopup = ref(false);
|
||||
const selectedCategory = ref('全部');
|
||||
const selectCategory=(uuid)=>{
|
||||
console.log(222)
|
||||
console.log(uuid)
|
||||
typeUuid.value=uuid;
|
||||
typeName.value=videoTypeList.value.find(item=>item.uuid==uuid).name;
|
||||
isAllActive.value=true;
|
||||
closeAllVideoPopup();
|
||||
currentPage.value = 1;
|
||||
hasMoreData.value = true;
|
||||
loadVideoData();
|
||||
}
|
||||
|
||||
const videoTypeList=ref([]);
|
||||
const patientVideoNew=async ()=>{
|
||||
const res=await api.patientVideoNew();
|
||||
if(res.code==200){
|
||||
videoTypeList.value=res.data;
|
||||
}
|
||||
}
|
||||
// 页面加载
|
||||
onMounted(() => {
|
||||
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
// 调用真实API获取数据
|
||||
currentPage.value = 1;
|
||||
hasMoreData.value = true;
|
||||
loadGuideTags();
|
||||
loadVideoData();
|
||||
patientVideoNew();
|
||||
});
|
||||
|
||||
// 加载轮播数据
|
||||
|
||||
// 加载视频标签
|
||||
const loadGuideTags = async () => {
|
||||
try {
|
||||
const res = await api.guideTag({
|
||||
type:5
|
||||
});
|
||||
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 loadVideoData = async (isRefresh = false) => {
|
||||
if (loading.value && !isRefresh) return;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.patientVideoByKeyWordsNew({
|
||||
page: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
keywords: keywords.value,
|
||||
sort:sort.value,
|
||||
typeUuid:typeUuid.value,
|
||||
});
|
||||
|
||||
console.log('视频列表API原始响应:', response);
|
||||
|
||||
// 检查响应结构
|
||||
let videoData = {};
|
||||
if (response && response.data) {
|
||||
if (response.data.code === 200) {
|
||||
videoData = response.data.data || {};
|
||||
} else if (response.code === 200) {
|
||||
videoData = response.data || {};
|
||||
}
|
||||
} else if (response && response.code === 200) {
|
||||
videoData = response.data || {};
|
||||
}
|
||||
|
||||
console.log('解析后的视频数据:', videoData);
|
||||
|
||||
if (videoData && videoData.list) {
|
||||
const { list, totalPage,pageNumber } = videoData;
|
||||
console.log('视频列表数据:', list);
|
||||
|
||||
if (isRefresh) {
|
||||
videoList.value = list || [];
|
||||
currentPage.value = 1;
|
||||
} else {
|
||||
videoList.value = [...videoList.value, ...(list || [])];
|
||||
}
|
||||
|
||||
hasMoreData.value = totalPage>pageNumber;
|
||||
loadMoreStatus.value = hasMoreData.value ? 'more' : 'noMore';
|
||||
} else {
|
||||
throw new Error(response?.message || '获取数据失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载视频失败:', error);
|
||||
|
||||
} finally {
|
||||
loading.value = false;
|
||||
refreshing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 下拉刷新
|
||||
const onRefresh = () => {
|
||||
refreshing.value = true;
|
||||
currentPage.value = 1;
|
||||
hasMoreData.value = true;
|
||||
loadVideoData(true);
|
||||
};
|
||||
|
||||
// 上拉加载更多
|
||||
const onLoadMore = () => {
|
||||
if (!hasMoreData.value || loading.value) return;
|
||||
|
||||
loadMoreStatus.value = 'loading';
|
||||
currentPage.value++;
|
||||
loadVideoData();
|
||||
};
|
||||
|
||||
// 切换标签
|
||||
const switchTab = (tabIndex) => {
|
||||
activeTab.value = tabIndex;
|
||||
// 可以根据标签加载不同的数据
|
||||
currentPage.value = 1;
|
||||
hasMoreData.value = true;
|
||||
loadVideoData();
|
||||
};
|
||||
|
||||
// 播放视频
|
||||
const playVideo = (video) => {
|
||||
uni.$emit('videoItem', video);
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 搜索
|
||||
const goSearch = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages_app/search/search'
|
||||
});
|
||||
};
|
||||
|
||||
// 跳转到患教视频列表
|
||||
|
||||
// 搜索视频
|
||||
const searchVideos = async (keywords) => {
|
||||
if (!keywords || keywords.trim() === '') {
|
||||
loadVideoData();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('搜索关键词:', keywords.trim());
|
||||
const response = await api.patientVideoByKeyWordsNew({
|
||||
keywords: keywords.trim(),
|
||||
page: 1,
|
||||
pageSize: pageSize.value
|
||||
});
|
||||
|
||||
console.log('搜索API原始响应:', response);
|
||||
|
||||
// 检查响应结构
|
||||
let searchData = {};
|
||||
if (response && response.data) {
|
||||
if (response.data.code === 200) {
|
||||
searchData = response.data.data || {};
|
||||
} else if (response.code === 200) {
|
||||
searchData = response.data || {};
|
||||
}
|
||||
} else if (response && response.code === 200) {
|
||||
searchData = response.data || {};
|
||||
}
|
||||
|
||||
console.log('解析后的搜索数据:', searchData);
|
||||
|
||||
if (searchData && searchData.list) {
|
||||
const { list, hasMore } = searchData;
|
||||
console.log('搜索结果:', list);
|
||||
videoList.value = list || [];
|
||||
hasMoreData.value = hasMore !== false;
|
||||
loadMoreStatus.value = hasMoreData.value ? 'more' : 'noMore';
|
||||
currentPage.value = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索视频失败:', error);
|
||||
uni.showToast({
|
||||
title: '搜索失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// 全部视频弹窗相关方法
|
||||
const openAllVideoPopup = () => {
|
||||
showAllVideoPopup.value = true;
|
||||
};
|
||||
|
||||
const closeAllVideoPopup = () => {
|
||||
showAllVideoPopup.value = false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 筛选弹窗相关方法
|
||||
const showFilterPopup = () => {
|
||||
showFilter.value = 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);
|
||||
// 关闭筛选激活态
|
||||
|
||||
// 清空关键字
|
||||
keywords.value = '';
|
||||
// 刷新列表数据(可按需保留或移除)
|
||||
currentPage.value = 1;
|
||||
hasMoreData.value = true;
|
||||
loadVideoData(true);
|
||||
};
|
||||
|
||||
const confirmFilter = () => {
|
||||
const selectedTags = filterTags.value.filter(tag => tag.selected);
|
||||
console.log('选中的筛选标签:', selectedTags);
|
||||
for (var i = 0; i < selectedTags.length; i++) {
|
||||
if(keywords.value){
|
||||
keywords.value+=","+selectedTags[i].NAME
|
||||
}else{
|
||||
keywords.value=selectedTags[i].NAME
|
||||
}
|
||||
}
|
||||
isFilterActive.value=true;
|
||||
hideFilterPopup();
|
||||
// 根据选中的标签重新加载数据
|
||||
currentPage.value = 1;
|
||||
hasMoreData.value = true;
|
||||
loadVideoData(true);
|
||||
};
|
||||
|
||||
// 返回
|
||||
const goBack = () => {
|
||||
uni.navigateBack({
|
||||
fail() {
|
||||
uni.redirectTo({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
const toggleSort = () => {
|
||||
sort.value = sort.value === 1 ? 2 : 1; // 1=最新, 2=最热(与后端约定)
|
||||
currentPage.value = 1;
|
||||
hasMoreData.value = true;
|
||||
loadVideoData();
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
$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;
|
||||
.video-page {
|
||||
background-color: #f5f5f5;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
}
|
||||
.divider {
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background-color: $gray-medium;
|
||||
margin: 0 $padding;
|
||||
}
|
||||
|
||||
/* Header Styles */
|
||||
.header {
|
||||
background-color: #fff;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-left, .header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #8B4513;
|
||||
}
|
||||
|
||||
/* Scroll View */
|
||||
.scroll-view {
|
||||
position: fixed;
|
||||
bottom: 0rpx;
|
||||
height: calc(100vh - 140rpx); /* 固定高度,减去导航栏高度 */
|
||||
background-color: #f5f5f5;
|
||||
top: 172rpx; /* 为导航栏留出空间 */
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Filter Tabs */
|
||||
.filter-tabs {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content:center;
|
||||
padding: 30rpx 30rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex:1;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.tab-item:last-child{
|
||||
margin-right: 0;
|
||||
}
|
||||
.right{
|
||||
flex:1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-text {
|
||||
color: #E74C3C;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
margin: 0 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
.tab-item:first-child .tab-text{
|
||||
max-width:155rpx;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tab-item .tab-text.active {
|
||||
color: #8B2316;
|
||||
|
||||
}
|
||||
|
||||
/* Fixed Filter Tabs Container */
|
||||
.filter-tabs-container {
|
||||
position: fixed;
|
||||
top: 140rpx; /* uni-nav-bar height */
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 40;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.doctor-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.doctor-avatar image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Video List */
|
||||
.video-list {
|
||||
padding: 0 20rpx;
|
||||
margin-top: 100rpx; /* 为固定的Filter Tabs留出空间 */
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx; /* 卡片间距 */
|
||||
}
|
||||
|
||||
.video-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 0;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
|
||||
width: calc(50% - 8rpx); /* 两列布局,减半gap */
|
||||
min-width: 300rpx; /* 确保最小宽度 */
|
||||
flex-shrink: 0; /* 防止收缩 */
|
||||
}
|
||||
|
||||
.video-thumbnail {
|
||||
position: relative;
|
||||
height: 200rpx;
|
||||
}
|
||||
|
||||
.video-thumbnail image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.video-duration {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.video-duration text {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.video-info {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.video-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 16rpx;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
overflow: hidden;
|
||||
height: 2.8em; /* 固定2行高度:1.4 * 2 = 2.8em */
|
||||
}
|
||||
|
||||
.video-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.author {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.view-count {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-state image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-state text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.newbox{
|
||||
margin-top: -12rpx;
|
||||
}
|
||||
|
||||
/* 全部视频弹窗样式 */
|
||||
.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;
|
||||
height:calc(100vh - 272rpx);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
// 筛选弹窗样式
|
||||
.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:272rpx;
|
||||
overflow-y:scroll;
|
||||
background-color: $white;
|
||||
// border-radius: 20rpx 20rpx 0 0;
|
||||
padding: 20rpx 30rpx 60rpx;
|
||||
width: 100%;
|
||||
height: calc(100vh - 272rpx);
|
||||
|
||||
.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 0; /* 去掉左右padding,仅保留上下 */
|
||||
border-radius: 30rpx;
|
||||
font-size: 26rpx;
|
||||
border: 2rpx solid #efefef;
|
||||
transition: all 0.3s ease;
|
||||
width: 152rpx; /* 固定宽度 */
|
||||
text-align: center; /* 文本居中 */
|
||||
white-space: nowrap; /* 单行显示 */
|
||||
overflow: hidden; /* 隐藏溢出 */
|
||||
text-overflow: ellipsis; /* 超出显示省略号 */
|
||||
|
||||
&.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;
|
||||
background-color: #fff; /* 背景色为白色 */
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
BIN
static/im_icon_camera_on.png
Normal file
BIN
static/im_icon_camera_on.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
static/im_icon_images_on.png
Normal file
BIN
static/im_icon_images_on.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
BIN
static/outpatient_true.png
Normal file
BIN
static/outpatient_true.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
BIN
static/quck_message.png
Normal file
BIN
static/quck_message.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
static/ytx_chatting_hospital.png
Normal file
BIN
static/ytx_chatting_hospital.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
static/ytx_chattingfooter_shopping.png
Normal file
BIN
static/ytx_chattingfooter_shopping.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
Loading…
x
Reference in New Issue
Block a user