This commit is contained in:
wangdongbo 2026-01-27 13:42:59 +08:00
parent 1332b12b03
commit 5d03f0fc79
14 changed files with 680 additions and 178 deletions

25
App.vue
View File

@ -29,13 +29,36 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
$primary-color: #0074ff; $primary-color: #0877F1;
page { page {
height: 100%; height: 100%;
background: #f5f5f5; background: #f5f5f5;
} }
/* 全局按钮样式 - 使用项目主题色 */
button[type="primary"],
.button-primary,
uni-button[type="primary"] {
background-color: $primary-color !important;
border-color: $primary-color !important;
color: #fff !important;
}
button[type="primary"]:not([disabled]):active,
.button-primary:active,
uni-button[type="primary"]:not([disabled]):active {
background-color: darken($primary-color, 10%) !important;
border-color: darken($primary-color, 10%) !important;
}
/* 微信小程序按钮样式覆盖 */
.wx-button[type="primary"] {
background-color: $primary-color !important;
border-color: $primary-color !important;
color: #fff !important;
}
.shadow-up { .shadow-up {
box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0.1) 0px -10px 15px -3px, rgba(0, 0, 0, 0.1) 0px -4px 6px -4px; rgba(0, 0, 0, 0.1) 0px -10px 15px -3px, rgba(0, 0, 0, 0.1) 0px -4px 6px -4px;

View File

@ -25,12 +25,24 @@
"navigationBarTitleText": "宣教文章" "navigationBarTitleText": "宣教文章"
} }
}, },
{
"path": "pages/message/article-detail",
"style": {
"navigationBarTitleText": "文章详情"
}
},
{ {
"path": "pages/message/survey-list", "path": "pages/message/survey-list",
"style": { "style": {
"navigationBarTitleText": "问卷列表" "navigationBarTitleText": "问卷列表"
} }
}, },
{
"path": "pages/webview/webview",
"style": {
"navigationBarTitleText": "预览"
}
},
{ {
"path": "pages/work/work", "path": "pages/work/work",
"style": { "style": {

View File

@ -0,0 +1,209 @@
<template>
<view class="article-detail-page">
<view v-if="loading" class="loading-container">
<uni-icons type="spinner-cycle" size="40" color="#999" />
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="error" class="error-container">
<text class="error-text">{{ error }}</text>
<button class="retry-btn" @click="loadArticle">重试</button>
</view>
<scroll-view v-else scroll-y class="article-content">
<view class="article-header">
<text class="article-title">{{ articleData.title }}</text>
<text class="article-date">{{ articleData.date }}</text>
</view>
<view class="article-body">
<view class="rich-text-wrapper">
<rich-text :nodes="articleData.content"></rich-text>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { onLoad } from '@dcloudio/uni-app';
import { getArticle } from '@/utils/api.js';
import { ref } from 'vue';
const loading = ref(true);
const error = ref('');
const articleData = ref({
title: '',
content: '',
date: ''
});
let articleId = '';
let corpId = '';
// 使
const processRichTextContent = (html) => {
if (!html) return '';
// img
let processedHtml = html.replace(
/<img/gi,
'<img style="max-width:100%;height:auto;display:block;margin:10px 0;"'
);
//
processedHtml = processedHtml.replace(
/style="[^"]*width:\s*\d+px[^"]*"/gi,
(match) => {
return match.replace(/width:\s*\d+px;?/gi, 'max-width:100%;');
}
);
//
processedHtml = processedHtml.replace(
/<table/gi,
'<table style="max-width:100%;overflow-x:auto;display:block;"'
);
//
processedHtml = `<div style="width:100%;overflow-x:hidden;word-wrap:break-word;word-break:break-all;">${processedHtml}</div>`;
return processedHtml;
};
//
const loadArticle = async () => {
if (!articleId || !corpId) {
error.value = '文章信息不完整';
loading.value = false;
return;
}
loading.value = true;
error.value = '';
try {
const res = await getArticle({ id: articleId, corpId: corpId });
if (res.success && res.data) {
//
let date = '';
if (res.data.createTime) {
const d = new Date(res.data.createTime);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
date = `${year}-${month}-${day}`;
}
articleData.value = {
title: res.data.title || '宣教文章',
content: processRichTextContent(res.data.content || ''),
date: date
};
} else {
error.value = res.message || '加载文章失败';
}
} catch (err) {
console.error('加载文章失败:', err);
error.value = '加载失败,请重试';
} finally {
loading.value = false;
}
};
onLoad((options) => {
if (options.id && options.corpId) {
articleId = options.id;
corpId = options.corpId;
loadArticle();
} else {
error.value = '文章信息不完整';
loading.value = false;
}
});
</script>
<style scoped lang="scss">
.article-detail-page {
width: 100%;
height: 100vh;
background-color: #fff;
}
.loading-container,
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
padding: 40rpx;
}
.loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #999;
}
.error-text {
font-size: 28rpx;
color: #999;
margin-bottom: 30rpx;
text-align: center;
}
.retry-btn {
padding: 16rpx 60rpx;
background-color: #0877F1;
color: #fff;
border: none;
border-radius: 8rpx;
font-size: 28rpx;
}
.article-content {
height: 100vh;
}
.article-header {
padding: 40rpx 30rpx 20rpx;
border-bottom: 1px solid #eee;
}
.article-title {
display: block;
font-size: 36rpx;
font-weight: bold;
color: #333;
line-height: 1.6;
margin-bottom: 20rpx;
}
.article-date {
display: block;
font-size: 24rpx;
color: #999;
}
.article-body {
padding: 0;
}
.rich-text-wrapper {
padding: 30rpx;
width: 100%;
box-sizing: border-box;
overflow: hidden;
}
.rich-text-wrapper ::v-deep rich-text {
width: 100%;
}
.rich-text-wrapper ::v-deep rich-text img {
max-width: 100% !important;
height: auto !important;
display: block;
}
</style>

View File

@ -19,10 +19,10 @@
v-for="cate in categoryList" v-for="cate in categoryList"
:key="cate._id || 'all'" :key="cate._id || 'all'"
class="category-item" class="category-item"
:class="{ active: currentCateId === (cate._id || 'all') }" :class="{ active: currentCateId === cate._id }"
@click="selectCategory(cate)" @click="selectCategory(cate)"
> >
{{ cate.name }} {{ cate.label }}
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
@ -54,17 +54,17 @@
> >
<view class="article-content" @click="previewArticle(article)"> <view class="article-content" @click="previewArticle(article)">
<text class="article-title">{{ article.title }}</text> <text class="article-title">{{ article.title }}</text>
<text class="article-date">创建时间{{ article.date }}</text> <view class="article-footer">
</view> <text class="article-date">{{ article.date }}</text>
<view class="article-action"> <button
<button class="send-btn"
class="send-btn" size="mini"
size="mini" type="primary"
type="primary" @click.stop="sendArticle(article)"
@click="sendArticle(article)" >
> 发送
发送 </button>
</button> </view>
</view> </view>
</view> </view>
@ -75,8 +75,7 @@
<view <view
v-if="!loading && articleList.length >= total" v-if="!loading && articleList.length >= total"
class="no-more" class="no-more">
>
没有更多了 没有更多了
</view> </view>
</view> </view>
@ -94,7 +93,9 @@
</view> </view>
</view> </view>
<scroll-view scroll-y class="preview-content"> <scroll-view scroll-y class="preview-content">
<rich-text :nodes="previewArticleData.content"></rich-text> <view class="rich-text-wrapper">
<rich-text :nodes="previewArticleData.content"></rich-text>
</view>
</scroll-view> </scroll-view>
<view class="preview-footer"> <view class="preview-footer">
<button class="preview-close-btn" @click="closePreview">关闭</button> <button class="preview-close-btn" @click="closePreview">关闭</button>
@ -120,8 +121,8 @@ const searchTitle = ref("");
let searchTimer = null; let searchTimer = null;
// //
const categoryList = ref([{ _id: "", name: "全部" }]); const categoryList = ref([{ _id: "", label: "全部" }]);
const currentCateId = ref(""); const currentCateId = ref(""); // ""_id
// //
const articleList = ref([]); const articleList = ref([]);
@ -143,7 +144,7 @@ const getCategoryList = async () => {
const res = await getArticleCateList({ corpId: corpId }); const res = await getArticleCateList({ corpId: corpId });
if (res.success && res.list) { if (res.success && res.list) {
const cates = res.list || []; const cates = res.list || [];
categoryList.value = [{ _id: "", name: "全部" }, ...cates]; categoryList.value = [{ _id: "", label: "全部" }, ...cates];
} }
} catch (error) { } catch (error) {
console.error("获取分类列表失败:", error); console.error("获取分类列表失败:", error);
@ -238,6 +239,36 @@ const loadMore = () => {
loadArticleList(); loadArticleList();
}; };
// 使
const processRichTextContent = (html) => {
if (!html) return '';
// img
let processedHtml = html.replace(
/<img/gi,
'<img style="max-width:100%;height:auto;display:block;margin:10px 0;"'
);
//
processedHtml = processedHtml.replace(
/style="[^"]*width:\s*\d+px[^"]*"/gi,
(match) => {
return match.replace(/width:\s*\d+px;?/gi, 'max-width:100%;');
}
);
//
processedHtml = processedHtml.replace(
/<table/gi,
'<table style="max-width:100%;overflow-x:auto;display:block;"'
);
//
processedHtml = `<div style="width:100%;overflow-x:hidden;word-wrap:break-word;word-break:break-all;">${processedHtml}</div>`;
return processedHtml;
};
// //
const previewArticle = async (article) => { const previewArticle = async (article) => {
try { try {
@ -245,10 +276,10 @@ const previewArticle = async (article) => {
const res = await getArticle({ id: article._id, corpId: corpId }); const res = await getArticle({ id: article._id, corpId: corpId });
uni.hideLoading(); uni.hideLoading();
if (res.success && res.data ) { if (res.success && res.data) {
previewArticleData.value = { previewArticleData.value = {
title: res.data.title || article.title, title: res.data.title || article.title,
content: res.data.content || "", content: processRichTextContent(res.data.content || ""),
}; };
previewPopup.value?.open(); previewPopup.value?.open();
} else { } else {
@ -358,18 +389,20 @@ onMounted(() => {
} }
.category-item { .category-item {
padding: 32rpx 24rpx; padding: 20rpx 24rpx;
font-size: 28rpx; font-size: 28rpx;
color: #333; color: #333;
text-align: center; text-align: center;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
transition: all 0.3s ease;
position: relative;
} }
.category-item.active { .category-item.active {
background-color: #fff; background-color: #fff;
color: #1890ff; color: #0877F1;
font-weight: bold; font-weight: bold;
border-left: 4rpx solid #1890ff; border-left: 4rpx solid #0877F1;
} }
.article-list { .article-list {
@ -397,39 +430,48 @@ onMounted(() => {
} }
.article-item { .article-item {
display: flex;
align-items: center;
padding: 24rpx 30rpx; padding: 24rpx 30rpx;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
transition: background-color 0.2s ease;
}
.article-item:active {
background-color: #f5f5f5;
} }
.article-content { .article-content {
flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-right: 20rpx; gap: 16rpx;
} }
.article-title { .article-title {
font-size: 28rpx; font-size: 28rpx;
color: #333; color: #333;
line-height: 1.5; line-height: 1.6;
word-break: break-all; word-break: break-all;
margin-bottom: 12rpx; font-weight: 500;
}
.article-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
} }
.article-date { .article-date {
flex: 1;
font-size: 24rpx; font-size: 24rpx;
color: #999; color: #999;
} }
.article-action {
flex-shrink: 0;
}
.send-btn { .send-btn {
padding: 8rpx 32rpx; flex-shrink: 0;
font-size: 26rpx; font-size: 26rpx;
padding: 8rpx 32rpx;
height: auto;
line-height: 1.4;
} }
.loading-more, .loading-more,
@ -486,10 +528,28 @@ onMounted(() => {
.preview-content { .preview-content {
flex: 1; flex: 1;
padding: 30rpx; padding: 0;
overflow-y: auto; overflow-y: auto;
} }
.rich-text-wrapper {
padding: 30rpx;
width: 100%;
box-sizing: border-box;
overflow: hidden;
}
/* rich-text 内部样式 */
.rich-text-wrapper ::v-deep rich-text {
width: 100%;
}
.rich-text-wrapper ::v-deep rich-text img {
max-width: 100% !important;
height: auto !important;
display: block;
}
.preview-footer { .preview-footer {
padding: 20rpx; padding: 20rpx;
border-top: 1px solid #eee; border-top: 1px solid #eee;

View File

@ -136,7 +136,7 @@ $primary-color: #0877F1;
.message-list { .message-list {
padding: 0 16rpx; padding: 0 16rpx;
padding-bottom: 20rpx; padding-bottom: 60rpx; /* 增加底部内边距,防止被小程序底部横线遮挡 */
} }
.message-item { .message-item {
@ -350,10 +350,10 @@ $primary-color: #0877F1;
flex: 1; flex: 1;
padding: 0 46rpx; padding: 0 46rpx;
background-color: #f3f5fa; background-color: #f3f5fa;
border-radius: 50rpx; border-radius: 20rpx;
margin: 0 16rpx; margin: 0 16rpx;
font-size: 28rpx; font-size: 28rpx;
height: 96rpx; height: 80rpx;
border: none; border: none;
outline: none; outline: none;
box-sizing: border-box; box-sizing: border-box;
@ -372,8 +372,8 @@ $primary-color: #0877F1;
justify-content: flex-start; justify-content: flex-start;
background: #fff; background: #fff;
border-top: 1rpx solid #eee; border-top: 1rpx solid #eee;
padding: 20rpx 0 8rpx 60rpx; padding: 20rpx 0 40rpx 60rpx;
gap: 40rpx; gap: 40rpx 50rpx;
flex-wrap: wrap; flex-wrap: wrap;
background-color: #f5f5f5; background-color: #f5f5f5;
} }

View File

@ -621,7 +621,7 @@ onMounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
// 使 // 使
$primary-color: #0877f1; $primary-color: #0877F1;
$primary-light: #e8f3ff; $primary-light: #e8f3ff;
$primary-gradient-start: #1b5cc8; $primary-gradient-start: #1b5cc8;
$primary-gradient-end: #0877f1; $primary-gradient-end: #0877f1;

View File

@ -76,7 +76,7 @@ const props = defineProps({
}); });
// Emits // Emits
const emit = defineEmits(["messageSent", "scrollToBottom"]); const emit = defineEmits(["messageSent", "scrollToBottom", "endConsult"]);
// //
const inputText = ref(""); const inputText = ref("");
@ -375,6 +375,24 @@ const goToSurveyList = () => {
}); });
}; };
//
const handleEndConsult = () => {
uni.showModal({
title: '确认结束问诊',
content: '确定要结束本次问诊吗?结束后将无法继续对话。',
confirmText: '确定结束',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
//
showMorePanel.value = false;
//
emit('endConsult');
}
}
});
};
const morePanelButtons = [ const morePanelButtons = [
{ text: "照片", icon: "/static/icon/zhaopian.png", action: showImagePicker }, { text: "照片", icon: "/static/icon/zhaopian.png", action: showImagePicker },
{ {
@ -400,7 +418,7 @@ const morePanelButtons = [
{ {
text: "结束问诊", text: "结束问诊",
icon: "/static/icon/jieshuzixun.png", icon: "/static/icon/jieshuzixun.png",
action: showImagePicker, action: handleEndConsult,
}, },
]; ];

View File

@ -242,12 +242,22 @@ const getArticleData = (message) => {
const handleArticleClick = (message) => { const handleArticleClick = (message) => {
const articleData = getArticleData(message); const articleData = getArticleData(message);
if (articleData.url) { if (articleData.url) {
// // IDcorpId
// const urlParams = new URLSearchParams(articleData.url.split('?')[1]);
console.log('打开文章:', articleData.url); const articleId = urlParams.get('id');
// uni.navigateTo({ const corpId = urlParams.get('corpId');
// url: `/pages/article/detail?url=${encodeURIComponent(articleData.url)}`
// }); if (articleId && corpId) {
//
uni.navigateTo({
url: `/pages/message/article-detail?id=${articleId}&corpId=${corpId}`
});
} else {
// ID使webview
uni.navigateTo({
url: `/pages/webview/webview?url=${encodeURIComponent(articleData.url)}`
});
}
} }
}; };

View File

@ -134,6 +134,7 @@
:formatTime="formatTime" :formatTime="formatTime"
@scrollToBottom="() => scrollToBottom(true)" @scrollToBottom="() => scrollToBottom(true)"
@messageSent="() => scrollToBottom(true)" @messageSent="() => scrollToBottom(true)"
@endConsult="handleEndConsult"
/> />
</view> </view>
</template> </template>
@ -161,7 +162,7 @@ import {
handleViewDetail, handleViewDetail,
checkIMConnectionStatus, checkIMConnectionStatus,
} from "@/utils/chat-utils.js"; } from "@/utils/chat-utils.js";
import { sendConsultRejectedMessage } from "@/utils/api.js"; import { sendConsultRejectedMessage, endConsultation } from "@/utils/api.js";
import useGroupChat from "./hooks/use-group-chat"; import useGroupChat from "./hooks/use-group-chat";
import MessageTypes from "./components/message-types.vue"; import MessageTypes from "./components/message-types.vue";
import ChatInput from "./components/chat-input.vue"; import ChatInput from "./components/chat-input.vue";
@ -223,27 +224,28 @@ function isSystemMessage(message) {
try { try {
// payload.data // payload.data
if (message.payload?.data) { if (message.payload?.data) {
const data = typeof message.payload.data === 'string' const data =
? JSON.parse(message.payload.data) typeof message.payload.data === "string"
: message.payload.data; ? JSON.parse(message.payload.data)
: message.payload.data;
// //
if (data.type === 'system_message') { if (data.type === "system_message") {
return true; return true;
} }
} }
// description // description
if (message.payload?.description === '系统消息标记') { if (message.payload?.description === "系统消息标记") {
return true; return true;
} }
// //
if (message.payload?.description === 'SYSTEM_NOTIFICATION') { if (message.payload?.description === "SYSTEM_NOTIFICATION") {
return true; return true;
} }
} catch (error) { } catch (error) {
console.error('判断系统消息失败:', error); console.error("判断系统消息失败:", error);
} }
return false; return false;
@ -254,33 +256,39 @@ function checkConsultPendingStatus() {
// //
for (let i = messageList.value.length - 1; i >= 0; i--) { for (let i = messageList.value.length - 1; i >= 0; i--) {
const message = messageList.value[i]; const message = messageList.value[i];
if (message.type === "TIMCustomElem" && message.payload?.data) { if (message.type === "TIMCustomElem" && message.payload?.data) {
try { try {
const data = typeof message.payload.data === 'string' const data =
? JSON.parse(message.payload.data) typeof message.payload.data === "string"
: message.payload.data; ? JSON.parse(message.payload.data)
: message.payload.data;
// consult_pending // consult_pending
if (data.type === 'system_message' && data.messageType === 'consult_pending') { if (
data.type === "system_message" &&
data.messageType === "consult_pending"
) {
showConsultAccept.value = true; showConsultAccept.value = true;
return; return;
} }
// //
if (data.type === 'system_message' && if (
(data.messageType === 'consult_accepted' || data.type === "system_message" &&
data.messageType === 'consult_ended' || (data.messageType === "consult_accepted" ||
data.messageType === 'consult_rejected')) { data.messageType === "consult_ended" ||
data.messageType === "consult_rejected")
) {
showConsultAccept.value = false; showConsultAccept.value = false;
return; return;
} }
} catch (error) { } catch (error) {
console.error('解析系统消息失败:', error); console.error("解析系统消息失败:", error);
} }
} }
} }
// //
showConsultAccept.value = false; showConsultAccept.value = false;
} }
@ -310,6 +318,7 @@ onLoad((options) => {
if (options.userID) { if (options.userID) {
chatInfo.value.userID = options.userID; chatInfo.value.userID = options.userID;
} }
checkLoginAndInitTIM(); checkLoginAndInitTIM();
updateNavigationTitle(); updateNavigationTitle();
}); });
@ -374,10 +383,10 @@ const initTIMCallbacks = async () => {
if (!existingMessage) { if (!existingMessage) {
messageList.value.push(message); messageList.value.push(message);
console.log("✓ 添加消息到列表,当前消息数量:", messageList.value.length); console.log("✓ 添加消息到列表,当前消息数量:", messageList.value.length);
// //
checkConsultPendingStatus(); checkConsultPendingStatus();
// 使 // 使
nextTick(() => { nextTick(() => {
scrollToBottom(true); scrollToBottom(true);
@ -703,23 +712,23 @@ defineExpose({
const handleAcceptConsult = async () => { const handleAcceptConsult = async () => {
try { try {
uni.showLoading({ uni.showLoading({
title: '处理中...', title: "处理中...",
}); });
// //
const customMessage = { const customMessage = {
data: JSON.stringify({ data: JSON.stringify({
type: 'system_message', type: "system_message",
messageType: 'consult_accepted', messageType: "consult_accepted",
content: '医生已接诊', content: "医生已接诊",
timestamp: Date.now(), timestamp: Date.now(),
}), }),
description: '系统消息标记', description: "系统消息标记",
extension: '', extension: "",
}; };
const message = timChatManager.tim.createCustomMessage({ const message = timChatManager.tim.createCustomMessage({
to: chatInfo.value.conversationID.replace('GROUP', ''), to: chatInfo.value.conversationID.replace("GROUP", ""),
conversationType: timChatManager.TIM.TYPES.CONV_GROUP, conversationType: timChatManager.TIM.TYPES.CONV_GROUP,
payload: customMessage, payload: customMessage,
}); });
@ -730,18 +739,18 @@ const handleAcceptConsult = async () => {
showConsultAccept.value = false; showConsultAccept.value = false;
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: '已接受问诊', title: "已接受问诊",
icon: 'success', icon: "success",
}); });
} else { } else {
throw new Error(sendResult.message || '发送失败'); throw new Error(sendResult.message || "发送失败");
} }
} catch (error) { } catch (error) {
console.error('接受问诊失败:', error); console.error("接受问诊失败:", error);
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: error.message || '操作失败', title: error.message || "操作失败",
icon: 'none', icon: "none",
}); });
} }
}; };
@ -756,16 +765,16 @@ const handleRejectConsult = () => {
const handleRejectReasonConfirm = async (reason) => { const handleRejectReasonConfirm = async (reason) => {
try { try {
showRejectReasonModal.value = false; showRejectReasonModal.value = false;
uni.showLoading({ uni.showLoading({
title: '处理中...', title: "处理中...",
}); });
// //
const memberName = account.value?.name || '医生'; const memberName = account.value?.name || "医生";
// ID // ID
const groupId = chatInfo.value.conversationID.replace('GROUP', ''); const groupId = chatInfo.value.conversationID.replace("GROUP", "");
// //
const result = await sendConsultRejectedMessage({ const result = await sendConsultRejectedMessage({
@ -779,18 +788,18 @@ const handleRejectReasonConfirm = async (reason) => {
if (result.success) { if (result.success) {
showConsultAccept.value = false; showConsultAccept.value = false;
uni.showToast({ uni.showToast({
title: '已拒绝问诊', title: "已拒绝问诊",
icon: 'success', icon: "success",
}); });
} else { } else {
throw new Error(result.message || '发送失败'); throw new Error(result.message || "发送失败");
} }
} catch (error) { } catch (error) {
console.error('拒绝问诊失败:', error); console.error("拒绝问诊失败:", error);
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: error.message || '操作失败', title: error.message || "操作失败",
icon: 'none', icon: "none",
}); });
} }
}; };
@ -800,6 +809,44 @@ const handleRejectReasonCancel = () => {
showRejectReasonModal.value = false; showRejectReasonModal.value = false;
}; };
//
const handleEndConsult = async () => {
try {
uni.showLoading({
title: "处理中...",
});
// groupId
const result = await endConsultation({
groupId: groupId.value,
adminAccount: account.value?.userId || "",
extraData: {
endBy: account.value?.userId || "",
endByName: account.value?.name || "医生",
endReason: "问诊完成",
},
});
uni.hideLoading();
if (result.success) {
uni.showToast({
title: "问诊已结束",
icon: "success",
});
} else {
throw new Error(result.message || "操作失败");
}
} catch (error) {
console.error("结束问诊失败:", error);
uni.hideLoading();
uni.showToast({
title: error.message || "操作失败",
icon: "none",
});
}
};
// //
onUnmounted(() => { onUnmounted(() => {
clearMessageCache(); clearMessageCache();
@ -841,10 +888,10 @@ uni.$on("sendArticle", async (data) => {
data: JSON.stringify({ data: JSON.stringify({
type: "article", type: "article",
title: article.title || "宣教文章", title: article.title || "宣教文章",
desc: "宣教文章", desc: "点击查看详情",
url: articleUrl, url: articleUrl,
imgUrl: imgUrl: "https://796f-youcan-clouddev-1-8ewcqf31dbb2b5-1317294507.tcb.qcloud.la/other/article-icon.png",
"https://796f-youcan-clouddev-1-8ewcqf31dbb2b5-1317294507.tcb.qcloud.la/other/19-%E9%97%AE%E5%8D%B7.png?sign=55a4cd77c418b2c548b65792a2cf6bce&t=1701328694", articleId: article._id
}), }),
description: "ARTICLE", description: "ARTICLE",
extension: "", extension: "",

View File

@ -3,33 +3,41 @@
<view class="header"> <view class="header">
<view class="search-bar"> <view class="search-bar">
<uni-icons type="search" size="18" color="#999" /> <uni-icons type="search" size="18" color="#999" />
<input class="search-input" v-model="searchName" placeholder="输入问卷名称搜索" @input="handleSearch" /> <input
class="search-input"
v-model="searchName"
placeholder="输入问卷名称搜索"
@input="handleSearch"
/>
</view> </view>
</view> </view>
<view class="content"> <view class="content">
<view class="category-sidebar"> <view class="category-sidebar">
<scroll-view scroll-y class="category-scroll"> <scroll-view scroll-y class="category-scroll">
<view <view
v-for="cate in categoryList" v-for="cate in categoryList"
:key="cate._id || 'all'" :key="cate._id || 'all'"
class="category-item" class="category-item"
:class="{ active: currentCateId === (cate._id || 'all') }" :class="{ active: currentCateId === cate._id }"
@click="selectCategory(cate)" @click="selectCategory(cate)"
> >
{{ cate.name }} {{ cate.label }}
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
<view class="survey-list"> <view class="survey-list">
<scroll-view <scroll-view
scroll-y scroll-y
class="survey-scroll" class="survey-scroll"
@scrolltolower="loadMore" @scrolltolower="loadMore"
lower-threshold="50" lower-threshold="50"
> >
<view v-if="loading && surveyList.length === 0" class="loading-container"> <view
v-if="loading && surveyList.length === 0"
class="loading-container"
>
<uni-icons type="spinner-cycle" size="30" color="#999" /> <uni-icons type="spinner-cycle" size="30" color="#999" />
<text class="loading-text">加载中...</text> <text class="loading-text">加载中...</text>
</view> </view>
@ -39,17 +47,24 @@
</view> </view>
<view v-else> <view v-else>
<view <view
v-for="survey in surveyList" v-for="survey in surveyList"
:key="survey._id" :key="survey._id"
class="survey-item" class="survey-item"
> >
<view class="survey-content" @click="previewSurvey(survey)"> <view class="survey-content" @click="previewSurvey(survey)">
<text class="survey-title">{{ survey.name }}</text> <text class="survey-title">{{ survey.name }}</text>
<text class="survey-desc">{{ survey.description || '暂无问卷说明' }}</text> <text class="survey-desc">{{
survey.description || "暂无问卷说明"
}}</text>
</view> </view>
<view class="survey-action"> <view class="survey-action">
<button class="send-btn" size="mini" type="primary" @click="sendSurvey(survey)"> <button
class="send-btn"
size="mini"
type="primary"
@click="sendSurvey(survey)"
>
发送 发送
</button> </button>
</view> </view>
@ -67,31 +82,31 @@
</scroll-view> </scroll-view>
</view> </view>
</view> </view>
<view class="footer">
<button class="cancel-btn" @click="goBack">取消</button>
</view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref, onMounted } from "vue";
import { getSurveyCateList, getSurveyList, createSurveyRecord } from '@/utils/api.js'; import {
import useAccountStore from '@/store/account.js'; getSurveyCateList,
import EmptyData from '@/components/empty-data.vue'; getSurveyList,
createSurveyRecord,
} from "@/utils/api.js";
import useAccountStore from "@/store/account.js";
import EmptyData from "@/components/empty-data.vue";
const env = __VITE_ENV__; const env = __VITE_ENV__;
const accountStore = useAccountStore(); const accountStore = useAccountStore();
const corpId = env.MP_CORP_ID; const corpId = env.MP_CORP_ID;
const userId = ref(''); const userId = ref("");
// //
const searchName = ref(''); const searchName = ref("");
let searchTimer = null; let searchTimer = null;
// //
const categoryList = ref([{ _id: '', name: '全部' }]); const categoryList = ref([{ _id: "", label: "全部" }]);
const currentCateId = ref(''); const currentCateId = ref("");
// //
const surveyList = ref([]); const surveyList = ref([]);
@ -99,27 +114,24 @@ const loading = ref(false);
const page = ref(1); const page = ref(1);
const pageSize = 30; const pageSize = 30;
const total = ref(0); const total = ref(0);
const emptyText = ref(''); const emptyText = ref("");
// //
const getCategoryList = async () => { const getCategoryList = async () => {
try { try {
const res = await getSurveyCateList({ corpId: corpId }); const res = await getSurveyCateList({ corpId: corpId });
if (res.success && res.data) { if (res.success && res.list) {
const cates = res.data.list || []; const cates = res.list || [];
categoryList.value = [ categoryList.value = [{ _id: "", label: "全部" }, ...cates];
{ _id: '', name: '全部' },
...cates
];
} }
} catch (error) { } catch (error) {
console.error('获取分类列表失败:', error); console.error("获取分类列表失败:", error);
} }
}; };
// //
const selectCategory = (cate) => { const selectCategory = (cate) => {
currentCateId.value = cate._id || ''; currentCateId.value = cate._id || "";
page.value = 1; page.value = 1;
surveyList.value = []; surveyList.value = [];
loadSurveyList(); loadSurveyList();
@ -140,7 +152,7 @@ const handleSearch = () => {
// //
const loadSurveyList = async () => { const loadSurveyList = async () => {
if (loading.value) return; if (loading.value) return;
loading.value = true; loading.value = true;
try { try {
const params = { const params = {
@ -148,8 +160,8 @@ const loadSurveyList = async () => {
page: page.value, page: page.value,
pageSize: pageSize, pageSize: pageSize,
name: searchName.value.trim(), name: searchName.value.trim(),
status: 'enable', status: "enable",
showCount: false showCount: false,
}; };
// ID // ID
@ -158,8 +170,8 @@ const loadSurveyList = async () => {
} }
const res = await getSurveyList(params); const res = await getSurveyList(params);
if (res.success && res.data) { if (res.success && res) {
const { list = [], total: count = 0 } = res.data; const { list = [], total: count = 0 } = res;
if (page.value === 1) { if (page.value === 1) {
surveyList.value = list; surveyList.value = list;
@ -167,20 +179,20 @@ const loadSurveyList = async () => {
surveyList.value = [...surveyList.value, ...list]; surveyList.value = [...surveyList.value, ...list];
} }
total.value = count; total.value = count;
emptyText.value = '暂无问卷信息'; emptyText.value = "暂无问卷信息";
} else { } else {
emptyText.value = res.message || '加载失败'; emptyText.value = res.message || "加载失败";
uni.showToast({ uni.showToast({
title: res.message || '获取问卷列表失败', title: res.message || "获取问卷列表失败",
icon: 'none' icon: "none",
}); });
} }
} catch (error) { } catch (error) {
console.error('加载问卷列表失败:', error); console.error("加载问卷列表失败:", error);
emptyText.value = '加载失败'; emptyText.value = "加载失败";
uni.showToast({ uni.showToast({
title: '加载失败,请重试', title: "加载失败,请重试",
icon: 'none' icon: "none",
}); });
} finally { } finally {
loading.value = false; loading.value = false;
@ -196,17 +208,29 @@ const loadMore = () => {
// //
const previewSurvey = (survey) => { const previewSurvey = (survey) => {
// const timestamp = Date.now();
uni.showToast({ const previewUrl = `https://www.youcan365.com/surveyDev/#/pages/survey/survey?surveyId=${survey.surveyId}&t=${timestamp}`;
title: '预览功能开发中',
icon: 'none' // #ifdef H5
window.open(previewUrl, '_blank');
// #endif
// #ifdef MP-WEIXIN
uni.navigateTo({
url: `/pages/webview/webview?url=${encodeURIComponent(previewUrl)}`
}); });
// #endif
// #ifdef APP-PLUS
plus.runtime.openURL(previewUrl);
// #endif
}; };
// //
const generateRandomString = (length) => { const generateRandomString = (length) => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const chars =
let result = ''; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length)); result += chars.charAt(Math.floor(Math.random() * chars.length));
} }
@ -216,45 +240,44 @@ const generateRandomString = (length) => {
// //
const sendSurvey = async (survey) => { const sendSurvey = async (survey) => {
if (loading.value) return; if (loading.value) return;
try { try {
loading.value = true; loading.value = true;
// //
const doctorInfo = accountStore.doctorInfo; const doctorInfo = accountStore.doctorInfo;
userId.value = doctorInfo?.userid || accountStore.openid; userId.value = doctorInfo?.userid || accountStore.openid;
// ID // ID
const sendSurveyId = generateRandomString(10); const sendSurveyId = generateRandomString(10);
// //
const pages = getCurrentPages(); const pages = getCurrentPages();
const prevPage = pages[pages.length - 2]; const prevPage = pages[pages.length - 2];
// //
// 使 // 使
uni.$emit('sendSurvey', { uni.$emit("sendSurvey", {
survey: survey, survey: survey,
corpId: corpId, corpId: corpId,
userId: userId.value, userId: userId.value,
sendSurveyId: sendSurveyId sendSurveyId: sendSurveyId,
}); });
uni.showToast({ uni.showToast({
title: '已选择问卷', title: "已选择问卷",
icon: 'success' icon: "success",
}); });
// //
setTimeout(() => { setTimeout(() => {
uni.navigateBack(); uni.navigateBack();
}, 500); }, 500);
} catch (error) { } catch (error) {
console.error('发送问卷失败:', error); console.error("发送问卷失败:", error);
uni.showToast({ uni.showToast({
title: error.message || '发送失败', title: error.message || "发送失败",
icon: 'none' icon: "none",
}); });
} finally { } finally {
loading.value = false; loading.value = false;
@ -315,20 +338,21 @@ onMounted(() => {
.category-scroll { .category-scroll {
height: 100%; height: 100%;
} }
.category-item { .category-item {
padding: 32rpx 24rpx; padding: 20rpx 24rpx;
font-size: 28rpx; font-size: 28rpx;
color: #333; color: #333;
text-align: center; text-align: center;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
transition: all 0.3s ease;
position: relative;
} }
.category-item.active { .category-item.active {
background-color: #fff; background-color: #fff;
color: #1890ff; color: #0877f1;
font-weight: bold; font-weight: bold;
border-left: 4rpx solid #1890ff; border-left: 4rpx solid #0877f1;
} }
.survey-list { .survey-list {
@ -393,7 +417,6 @@ onMounted(() => {
} }
.send-btn { .send-btn {
padding: 8rpx 32rpx;
font-size: 26rpx; font-size: 26rpx;
} }

24
pages/webview/webview.vue Normal file
View File

@ -0,0 +1,24 @@
<template>
<view class="webview-container">
<web-view :src="url"></web-view>
</view>
</template>
<script setup>
import { onLoad } from "@dcloudio/uni-app";
import { ref } from "vue";
const url = ref("");
onLoad((options) => {
if (options.url) {
url.value = decodeURIComponent(options.url);
}
});
</script>
<style scoped>
.webview-container {
width: 100%;
height: 100vh;
}
</style>

58
styles/theme.scss Normal file
View File

@ -0,0 +1,58 @@
/**
* 项目主题色配置
* 统一管理项目中使用的主题色
*/
// 主题色
$primary-color: #0877F1;
$primary-light: #e8f3ff;
$primary-dark: #0660c9;
$primary-gradient-start: #1b5cc8;
$primary-gradient-end: #0877F1;
// 辅助色
$success-color: #4cd964;
$warning-color: #f0ad4e;
$error-color: #dd524d;
$info-color: #909399;
// 文字颜色
$text-color-primary: #333;
$text-color-regular: #666;
$text-color-secondary: #999;
$text-color-placeholder: #c0c4cc;
$text-color-white: #fff;
// 背景颜色
$bg-color-white: #ffffff;
$bg-color-page: #f5f5f5;
$bg-color-hover: #f1f1f1;
$bg-color-mask: rgba(0, 0, 0, 0.4);
// 边框颜色
$border-color-base: #e4e7ed;
$border-color-light: #ebeef5;
$border-color-lighter: #f2f6fc;
// 圆角
$border-radius-small: 4rpx;
$border-radius-base: 8rpx;
$border-radius-large: 16rpx;
$border-radius-round: 999rpx;
// 间距
$spacing-small: 16rpx;
$spacing-base: 24rpx;
$spacing-large: 32rpx;
// 字体大小
$font-size-small: 24rpx;
$font-size-base: 28rpx;
$font-size-medium: 32rpx;
$font-size-large: 36rpx;
$font-size-xlarge: 40rpx;
// 阴影
$box-shadow-light: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
$box-shadow-base: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
$box-shadow-dark: 0 8rpx 24rpx rgba(0, 0, 0, 0.12);

View File

@ -15,7 +15,7 @@
/* 颜色变量 */ /* 颜色变量 */
/* 行为相关颜色 */ /* 行为相关颜色 */
$uni-color-primary: #007aff; $uni-color-primary: #0877F1;
$uni-color-success: #4cd964; $uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e; $uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d; $uni-color-error: #dd524d;

View File

@ -61,7 +61,9 @@ const urlsConfig = {
getUserSig: 'getUserSig', getUserSig: 'getUserSig',
sendSystemMessage: "sendSystemMessage", sendSystemMessage: "sendSystemMessage",
getChatRecordsByGroupId: "getChatRecordsByGroupId", getChatRecordsByGroupId: "getChatRecordsByGroupId",
sendConsultRejectedMessage: "sendConsultRejectedMessage" sendConsultRejectedMessage: "sendConsultRejectedMessage",
endConsultation: "endConsultation",
getGroupListByGroupId: "getGroupListByGroupId"
} }
} }
@ -141,3 +143,19 @@ export async function sendConsultRejectedMessage(data) {
} }
}); });
} }
// 结束问诊接口
export async function endConsultation(data) {
return request({
url: '/getYoucanData/im',
data: {
type: 'endConsultation',
...data
}
});
}
// 根据群组ID获取群组记录
export async function getGroupListByGroupId(data) {
return api('getGroupListByGroupId', data);
}