Compare commits
2 Commits
25966f97f4
...
1332b12b03
| Author | SHA1 | Date | |
|---|---|---|---|
| 1332b12b03 | |||
| 255254c617 |
12
pages.json
12
pages.json
@ -19,6 +19,18 @@
|
|||||||
"navigationBarTitleText": "常用语"
|
"navigationBarTitleText": "常用语"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/message/article-list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "宣教文章"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/message/survey-list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "问卷列表"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/work/work",
|
"path": "pages/work/work",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
@ -1,95 +0,0 @@
|
|||||||
# 常用语功能说明
|
|
||||||
|
|
||||||
## 功能概述
|
|
||||||
在聊天页面添加了常用语功能,医生可以快速选择并发送预设的常用语内容。
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
### 1. 常用语列表页面 (`common-phrases.vue`)
|
|
||||||
- 支持分类管理(文字随访、语音随访、常用回复等)
|
|
||||||
- 支持添加、编辑、删除常用语
|
|
||||||
- 支持添加自定义分类
|
|
||||||
- 点击常用语直接发送到聊天
|
|
||||||
|
|
||||||
### 2. 聊天输入框集成
|
|
||||||
- 在聊天输入框的"更多"面板中添加"常用语"入口
|
|
||||||
- 点击后跳转到常用语列表页面
|
|
||||||
- 选择常用语后自动返回并发送
|
|
||||||
|
|
||||||
### 3. 数据持久化
|
|
||||||
- 常用语数据存储在MongoDB的`common-words`集合中
|
|
||||||
- 支持机构级别和个人级别的常用语
|
|
||||||
- 与PC端管理后台数据互通
|
|
||||||
|
|
||||||
## 文件结构
|
|
||||||
|
|
||||||
```
|
|
||||||
ykt-wx-app/pages/message/
|
|
||||||
├── common-phrases.vue # 常用语列表页面
|
|
||||||
├── components/
|
|
||||||
│ └── chat-input.vue # 聊天输入框组件(已更新)
|
|
||||||
├── index.vue # 聊天主页面(已更新)
|
|
||||||
└── README.md # 本文档
|
|
||||||
|
|
||||||
ykt-wx-app/utils/
|
|
||||||
└── api.js # API配置(已添加常用语接口)
|
|
||||||
|
|
||||||
ytk-customer-service/knowledgeBase/common-words/
|
|
||||||
└── index.js # 后端常用语接口(已添加小程序接口)
|
|
||||||
```
|
|
||||||
|
|
||||||
## API接口
|
|
||||||
|
|
||||||
### 1. 获取常用语列表
|
|
||||||
```javascript
|
|
||||||
api('getCommonPhrases', { corpId })
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 保存常用语
|
|
||||||
```javascript
|
|
||||||
api('saveCommonPhrase', {
|
|
||||||
id, // 可选,更新时传入
|
|
||||||
corpId,
|
|
||||||
userId,
|
|
||||||
categoryId,
|
|
||||||
content
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 删除常用语
|
|
||||||
```javascript
|
|
||||||
api('deleteCommonPhrase', {
|
|
||||||
id,
|
|
||||||
corpId
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 获取分类列表
|
|
||||||
```javascript
|
|
||||||
api('getCommonPhraseCategories', { corpId })
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 保存分类
|
|
||||||
```javascript
|
|
||||||
api('saveCommonPhraseCategory', {
|
|
||||||
corpId,
|
|
||||||
userId,
|
|
||||||
name
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用流程
|
|
||||||
|
|
||||||
1. 用户在聊天页面点击输入框右侧的"+"按钮
|
|
||||||
2. 在弹出的功能面板中点击"常用语"
|
|
||||||
3. 跳转到常用语列表页面
|
|
||||||
4. 可以切换分类查看不同类型的常用语
|
|
||||||
5. 点击任意常用语,自动返回聊天页面并发送该内容
|
|
||||||
6. 在编辑模式下可以添加、编辑、删除常用语
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. 常用语功能需要用户已登录并有有效的corpId
|
|
||||||
2. 如果后端API调用失败,会使用本地模拟数据
|
|
||||||
3. 常用语内容最多500字
|
|
||||||
4. 分类名称最多6个字
|
|
||||||
503
pages/message/article-list.vue
Normal file
503
pages/message/article-list.vue
Normal file
@ -0,0 +1,503 @@
|
|||||||
|
<template>
|
||||||
|
<view class="article-page">
|
||||||
|
<view class="header">
|
||||||
|
<view class="search-bar">
|
||||||
|
<uni-icons type="search" size="18" color="#999" />
|
||||||
|
<input
|
||||||
|
class="search-input"
|
||||||
|
v-model="searchTitle"
|
||||||
|
placeholder="输入内容名称搜索"
|
||||||
|
@input="handleSearch"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="content">
|
||||||
|
<view class="category-sidebar">
|
||||||
|
<scroll-view scroll-y class="category-scroll">
|
||||||
|
<view
|
||||||
|
v-for="cate in categoryList"
|
||||||
|
:key="cate._id || 'all'"
|
||||||
|
class="category-item"
|
||||||
|
:class="{ active: currentCateId === (cate._id || 'all') }"
|
||||||
|
@click="selectCategory(cate)"
|
||||||
|
>
|
||||||
|
{{ cate.name }}
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="article-list">
|
||||||
|
<scroll-view
|
||||||
|
scroll-y
|
||||||
|
class="article-scroll"
|
||||||
|
@scrolltolower="loadMore"
|
||||||
|
lower-threshold="50"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-if="loading && articleList.length === 0"
|
||||||
|
class="loading-container"
|
||||||
|
>
|
||||||
|
<uni-icons type="spinner-cycle" size="30" color="#999" />
|
||||||
|
<text class="loading-text">加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else-if="articleList.length === 0" class="empty-container">
|
||||||
|
<empty-data title="暂无文章" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else>
|
||||||
|
<view
|
||||||
|
v-for="article in articleList"
|
||||||
|
:key="article._id"
|
||||||
|
class="article-item"
|
||||||
|
>
|
||||||
|
<view class="article-content" @click="previewArticle(article)">
|
||||||
|
<text class="article-title">{{ article.title }}</text>
|
||||||
|
<text class="article-date">创建时间:{{ article.date }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="article-action">
|
||||||
|
<button
|
||||||
|
class="send-btn"
|
||||||
|
size="mini"
|
||||||
|
type="primary"
|
||||||
|
@click="sendArticle(article)"
|
||||||
|
>
|
||||||
|
发送
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="loading && articleList.length > 0" class="loading-more">
|
||||||
|
<uni-icons type="spinner-cycle" size="20" color="#999" />
|
||||||
|
<text>加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-if="!loading && articleList.length >= total"
|
||||||
|
class="no-more"
|
||||||
|
>
|
||||||
|
没有更多了
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 文章预览弹窗 -->
|
||||||
|
<uni-popup ref="previewPopup" type="bottom" :safe-area="false">
|
||||||
|
<view class="preview-container">
|
||||||
|
<view class="preview-header">
|
||||||
|
<text class="preview-title">{{ previewArticleData.title }}</text>
|
||||||
|
<view class="preview-close" @click="closePreview">
|
||||||
|
<uni-icons type="closeempty" size="24" color="#333" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="preview-content">
|
||||||
|
<rich-text :nodes="previewArticleData.content"></rich-text>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="preview-footer">
|
||||||
|
<button class="preview-close-btn" @click="closePreview">关闭</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { getArticleCateList, getArticleList, getArticle } from "@/utils/api.js";
|
||||||
|
import useAccountStore from "@/store/account.js";
|
||||||
|
import EmptyData from "@/components/empty-data.vue";
|
||||||
|
|
||||||
|
const env = __VITE_ENV__;
|
||||||
|
const accountStore = useAccountStore();
|
||||||
|
const corpId = env.MP_CORP_ID;
|
||||||
|
const userId = ref("");
|
||||||
|
|
||||||
|
// 搜索关键词
|
||||||
|
const searchTitle = ref("");
|
||||||
|
let searchTimer = null;
|
||||||
|
|
||||||
|
// 分类列表
|
||||||
|
const categoryList = ref([{ _id: "", name: "全部" }]);
|
||||||
|
const currentCateId = ref("");
|
||||||
|
|
||||||
|
// 文章列表
|
||||||
|
const articleList = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const page = ref(1);
|
||||||
|
const pageSize = 30;
|
||||||
|
const total = ref(0);
|
||||||
|
|
||||||
|
// 预览文章数据
|
||||||
|
const previewArticleData = ref({
|
||||||
|
title: "",
|
||||||
|
content: "",
|
||||||
|
});
|
||||||
|
const previewPopup = ref(null);
|
||||||
|
|
||||||
|
// 获取分类列表
|
||||||
|
const getCategoryList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getArticleCateList({ corpId: corpId });
|
||||||
|
if (res.success && res.list) {
|
||||||
|
const cates = res.list || [];
|
||||||
|
categoryList.value = [{ _id: "", name: "全部" }, ...cates];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取分类列表失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择分类
|
||||||
|
const selectCategory = (cate) => {
|
||||||
|
currentCateId.value = cate._id || "";
|
||||||
|
page.value = 1;
|
||||||
|
articleList.value = [];
|
||||||
|
loadArticleList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索处理
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (searchTimer) {
|
||||||
|
clearTimeout(searchTimer);
|
||||||
|
}
|
||||||
|
searchTimer = setTimeout(() => {
|
||||||
|
page.value = 1;
|
||||||
|
articleList.value = [];
|
||||||
|
loadArticleList();
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载文章列表
|
||||||
|
const loadArticleList = async () => {
|
||||||
|
if (loading.value) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
corpId: corpId,
|
||||||
|
page: page.value,
|
||||||
|
pageSize: pageSize,
|
||||||
|
enable: true,
|
||||||
|
title: searchTitle.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果选择了分类,添加分类ID
|
||||||
|
if (currentCateId.value) {
|
||||||
|
params.cateIds = [currentCateId.value];
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await getArticleList(params);
|
||||||
|
if (res.success && res.list) {
|
||||||
|
const { list = [], total: count = 0 } = res;
|
||||||
|
const formattedList = list.map((item) => {
|
||||||
|
// 格式化日期
|
||||||
|
let date = "";
|
||||||
|
if (item.createTime) {
|
||||||
|
const d = new Date(item.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}`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
date,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (page.value === 1) {
|
||||||
|
articleList.value = formattedList;
|
||||||
|
} else {
|
||||||
|
articleList.value = [...articleList.value, ...formattedList];
|
||||||
|
}
|
||||||
|
total.value = count;
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: res.message || "获取文章列表失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("加载文章列表失败:", error);
|
||||||
|
uni.showToast({
|
||||||
|
title: "加载失败,请重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载更多
|
||||||
|
const loadMore = () => {
|
||||||
|
if (loading.value || articleList.value.length >= total.value) return;
|
||||||
|
page.value += 1;
|
||||||
|
loadArticleList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 预览文章
|
||||||
|
const previewArticle = async (article) => {
|
||||||
|
try {
|
||||||
|
uni.showLoading({ title: "加载中..." });
|
||||||
|
const res = await getArticle({ id: article._id, corpId: corpId });
|
||||||
|
uni.hideLoading();
|
||||||
|
|
||||||
|
if (res.success && res.data ) {
|
||||||
|
previewArticleData.value = {
|
||||||
|
title: res.data.title || article.title,
|
||||||
|
content: res.data.content || "",
|
||||||
|
};
|
||||||
|
previewPopup.value?.open();
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: res.message || "预览文章失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading();
|
||||||
|
console.error("预览文章失败:", error);
|
||||||
|
uni.showToast({
|
||||||
|
title: "预览失败,请重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭预览
|
||||||
|
const closePreview = () => {
|
||||||
|
previewPopup.value?.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 发送文章
|
||||||
|
const sendArticle = (article) => {
|
||||||
|
// 获取当前页面栈
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
const prevPage = pages[pages.length - 2];
|
||||||
|
|
||||||
|
if (prevPage) {
|
||||||
|
// 获取医生信息
|
||||||
|
const doctorInfo = accountStore.doctorInfo;
|
||||||
|
userId.value = doctorInfo?.userid || accountStore.openid;
|
||||||
|
|
||||||
|
// 通过事件总线传递数据
|
||||||
|
uni.$emit("sendArticle", {
|
||||||
|
article: article,
|
||||||
|
corpId: corpId,
|
||||||
|
userId: userId.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: "已选择文章",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 延迟返回,让用户看到提示
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 返回
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getCategoryList();
|
||||||
|
loadArticleList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.article-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20rpx;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 16rpx 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-sidebar {
|
||||||
|
width: 200rpx;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-right: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-scroll {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-item {
|
||||||
|
padding: 32rpx 24rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-item.active {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #1890ff;
|
||||||
|
font-weight: bold;
|
||||||
|
border-left: 4rpx solid #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-list {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-scroll {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container,
|
||||||
|
.empty-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 100rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx 30rpx;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-break: break-all;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-date {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-action {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn {
|
||||||
|
padding: 8rpx 32rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-more,
|
||||||
|
.no-more {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 30rpx 0;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
gap: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20rpx;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 预览弹窗样式 */
|
||||||
|
.preview-container {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 20rpx 20rpx 0 0;
|
||||||
|
height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 30rpx;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-title {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-close {
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 30rpx;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-footer {
|
||||||
|
padding: 20rpx;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-close-btn {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #1890ff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1249,3 +1249,111 @@ $primary-color: #0877F1;
|
|||||||
0%, 100% { transform: translateY(0); }
|
0%, 100% { transform: translateY(0); }
|
||||||
50% { transform: translateY(-10rpx); }
|
50% { transform: translateY(-10rpx); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 文章卡片样式 */
|
||||||
|
.article-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
max-width: 500rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-image {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文章卡片在不同消息流中的样式 */
|
||||||
|
.message-right .article-card {
|
||||||
|
background-color: #e8f4ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-left .article-card {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 问卷卡片样式 */
|
||||||
|
.survey-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
max-width: 500rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-image {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 问卷卡片在不同消息流中的样式 */
|
||||||
|
.message-right .survey-card {
|
||||||
|
background-color: #e8f4ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-left .survey-card {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|||||||
@ -5,11 +5,11 @@
|
|||||||
<view class="category-sidebar">
|
<view class="category-sidebar">
|
||||||
<scroll-view class="category-list" scroll-y>
|
<scroll-view class="category-list" scroll-y>
|
||||||
<view
|
<view
|
||||||
v-for="category in categories"
|
v-for="category in allCategories"
|
||||||
:key="category.id"
|
:key="category.id"
|
||||||
class="category-item"
|
class="category-item"
|
||||||
:class="{ active: currentCategory === category.id }"
|
:class="{ active: currentCategory === category.id }"
|
||||||
@click="switchCategory(category.id)"
|
@click="handleCategoryClick(category)"
|
||||||
@longpress="handleCategoryLongPress(category)"
|
@longpress="handleCategoryLongPress(category)"
|
||||||
>
|
>
|
||||||
<view class="category-content">
|
<view class="category-content">
|
||||||
@ -23,26 +23,16 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="add-category-item" @click="showAddCategoryDialog">
|
|
||||||
<text class="plus-icon">+</text>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 新增分类按钮(固定在底部) -->
|
||||||
|
<view class="add-category-footer" @click="showAddCategoryDialog">
|
||||||
|
<text class="add-category-text">新增分类</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 右侧常用语列表 -->
|
<!-- 右侧常用语列表 -->
|
||||||
<view class="phrases-container">
|
<view class="phrases-container">
|
||||||
<!-- 顶部操作栏 -->
|
|
||||||
<view class="action-bar">
|
|
||||||
<view class="add-phrase-btn" @click="showAddPhraseDialog">
|
|
||||||
<text class="add-icon">+</text>
|
|
||||||
<text class="add-text">添加快捷回复</text>
|
|
||||||
</view>
|
|
||||||
<view class="edit-btn" @click="toggleEditMode">
|
|
||||||
<text class="edit-text">{{ isEditMode ? "完成" : "编辑" }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<scroll-view class="phrases-list" scroll-y>
|
<scroll-view class="phrases-list" scroll-y>
|
||||||
<view
|
<view
|
||||||
v-for="phrase in currentPhrases"
|
v-for="phrase in currentPhrases"
|
||||||
@ -66,9 +56,20 @@
|
|||||||
|
|
||||||
<view v-if="currentPhrases.length === 0" class="empty-state">
|
<view v-if="currentPhrases.length === 0" class="empty-state">
|
||||||
<text class="empty-text">暂无常用语</text>
|
<text class="empty-text">暂无常用语</text>
|
||||||
<text class="empty-hint">点击上方按钮添加常用语</text>
|
<text class="empty-hint">点击下方按钮添加常用语</text>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 底部操作栏(页脚) -->
|
||||||
|
<view class="footer-action-bar">
|
||||||
|
<view class="add-phrase-btn" @click="showAddPhraseDialog">
|
||||||
|
<text class="add-icon">+</text>
|
||||||
|
<text class="add-text">添加快捷回复</text>
|
||||||
|
</view>
|
||||||
|
<view class="edit-btn" @click="toggleEditMode">
|
||||||
|
<text class="edit-text">{{ isEditMode ? "完成" : "编辑" }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -108,7 +109,9 @@
|
|||||||
>
|
>
|
||||||
<view class="popup-content category-popup" @click.stop>
|
<view class="popup-content category-popup" @click.stop>
|
||||||
<view class="popup-header">
|
<view class="popup-header">
|
||||||
<text class="popup-title">新建分类</text>
|
<text class="popup-title">{{
|
||||||
|
editingCategory ? "编辑分类" : "新建分类"
|
||||||
|
}}</text>
|
||||||
<view class="popup-close" @click="closeCategoryPopup">
|
<view class="popup-close" @click="closeCategoryPopup">
|
||||||
<text class="close-icon">×</text>
|
<text class="close-icon">×</text>
|
||||||
</view>
|
</view>
|
||||||
@ -117,11 +120,12 @@
|
|||||||
v-model="categoryForm.name"
|
v-model="categoryForm.name"
|
||||||
class="category-input"
|
class="category-input"
|
||||||
placeholder="请输入分类名,最多6个字"
|
placeholder="请输入分类名,最多6个字"
|
||||||
maxlength="6"
|
|
||||||
/>
|
/>
|
||||||
<view class="popup-actions">
|
<view class="popup-actions">
|
||||||
<button class="cancel-btn" @click="closeCategoryPopup">取消</button>
|
<button class="cancel-btn" @click="closeCategoryPopup">取消</button>
|
||||||
<button class="confirm-btn" @click="saveCategory">确认添加</button>
|
<button class="confirm-btn" @click="saveCategory">
|
||||||
|
{{ editingCategory ? "保存" : "确认添加" }}
|
||||||
|
</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -130,7 +134,12 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from "vue";
|
import { ref, computed, onMounted } from "vue";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
import api from "@/utils/api";
|
import api from "@/utils/api";
|
||||||
|
import useAccountStore from "@/store/account";
|
||||||
|
|
||||||
|
// 获取 store
|
||||||
|
const { doctorInfo } = storeToRefs(useAccountStore());
|
||||||
|
|
||||||
// 获取系统状态栏高度
|
// 获取系统状态栏高度
|
||||||
const statusBarHeight = ref(0);
|
const statusBarHeight = ref(0);
|
||||||
@ -138,21 +147,64 @@ const statusBarHeight = ref(0);
|
|||||||
// 编辑模式
|
// 编辑模式
|
||||||
const isEditMode = ref(false);
|
const isEditMode = ref(false);
|
||||||
|
|
||||||
// 分类数据
|
// 默认分类(虚拟分类,不存在于数据库)
|
||||||
const categories = ref([
|
const DEFAULT_CATEGORY = {
|
||||||
{ id: "visit", name: "文字随访", deletable: false },
|
id: "__default__",
|
||||||
{ id: "voice", name: "语音随访", deletable: false },
|
name: "默认",
|
||||||
{ id: "common", name: "常用回复", deletable: false },
|
sort: -1,
|
||||||
]);
|
deletable: false,
|
||||||
|
type: "personal",
|
||||||
|
};
|
||||||
|
|
||||||
const currentCategory = ref("common");
|
// 分类数据(初始为空,从后台加载)
|
||||||
|
const categories = ref([]);
|
||||||
|
|
||||||
|
// 机构分类数据
|
||||||
|
const corpCategories = ref([]);
|
||||||
|
|
||||||
|
// 所有分类(包含默认分类 + 个人分类 + 机构分类)
|
||||||
|
const allCategories = computed(() => {
|
||||||
|
// 个人分类
|
||||||
|
const personalCats = [DEFAULT_CATEGORY, ...categories.value];
|
||||||
|
|
||||||
|
// 机构分类(标记为只读)
|
||||||
|
const corpCats = corpCategories.value.map(cat => ({
|
||||||
|
...cat,
|
||||||
|
type: "corp",
|
||||||
|
deletable: false,
|
||||||
|
isCorpCategory: true, // 标记为机构分类
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [...personalCats, ...corpCats];
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentCategory = ref(DEFAULT_CATEGORY.id);
|
||||||
|
|
||||||
// 常用语数据
|
// 常用语数据
|
||||||
const phrases = ref([]);
|
const phrases = ref([]);
|
||||||
|
|
||||||
|
// 机构常用语数据
|
||||||
|
const corpPhrases = ref([]);
|
||||||
|
|
||||||
// 当前分类的常用语
|
// 当前分类的常用语
|
||||||
const currentPhrases = computed(() => {
|
const currentPhrases = computed(() => {
|
||||||
return phrases.value.filter((p) => p.categoryId === currentCategory.value);
|
// 获取当前分类信息
|
||||||
|
const currentCat = allCategories.value.find(c => c.id === currentCategory.value);
|
||||||
|
|
||||||
|
if (currentCategory.value === DEFAULT_CATEGORY.id) {
|
||||||
|
// 默认分类显示没有 categoryId 或 categoryId 为空的个人常用语
|
||||||
|
return phrases.value.filter((p) => !p.categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是机构分类,返回机构常用语
|
||||||
|
if (currentCat?.isCorpCategory) {
|
||||||
|
return corpPhrases.value.filter((p) => p.categoryId === currentCategory.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 个人分类,返回个人常用语
|
||||||
|
const filtered = phrases.value.filter((p) => p.categoryId === currentCategory.value);
|
||||||
|
console.log("当前分类:", currentCategory.value);
|
||||||
|
return filtered;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 弹窗显示状态
|
// 弹窗显示状态
|
||||||
@ -169,6 +221,7 @@ const categoryForm = ref({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const editingPhrase = ref(null);
|
const editingPhrase = ref(null);
|
||||||
|
const editingCategory = ref(null);
|
||||||
|
|
||||||
// 返回上一页
|
// 返回上一页
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
@ -185,6 +238,24 @@ const switchCategory = (categoryId) => {
|
|||||||
currentCategory.value = categoryId;
|
currentCategory.value = categoryId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理分类点击(编辑模式下编辑分类名称)
|
||||||
|
const handleCategoryClick = (category) => {
|
||||||
|
if (isEditMode.value && category.deletable) {
|
||||||
|
// 编辑模式下,点击可编辑的分类,弹出编辑框
|
||||||
|
editCategory(category);
|
||||||
|
} else {
|
||||||
|
// 非编辑模式,切换分类
|
||||||
|
switchCategory(category.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑分类
|
||||||
|
const editCategory = (category) => {
|
||||||
|
editingCategory.value = category;
|
||||||
|
categoryForm.value.name = category.name;
|
||||||
|
showCategoryPopup.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
// 处理常用语点击
|
// 处理常用语点击
|
||||||
const handlePhraseClick = (phrase) => {
|
const handlePhraseClick = (phrase) => {
|
||||||
if (isEditMode.value) {
|
if (isEditMode.value) {
|
||||||
@ -220,63 +291,79 @@ const editPhrase = (phrase) => {
|
|||||||
const savePhrase = async () => {
|
const savePhrase = async () => {
|
||||||
if (!phraseForm.value.content.trim()) {
|
if (!phraseForm.value.content.trim()) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '请输入内容',
|
title: "请输入内容",
|
||||||
icon: 'none'
|
icon: "none",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const corpId = uni.getStorageSync('corpId');
|
if (!doctorInfo.value) {
|
||||||
const userId = uni.getStorageSync('userId');
|
|
||||||
|
|
||||||
if (!corpId || !userId) {
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '请先登录',
|
title: "请先登录",
|
||||||
icon: 'none'
|
icon: "none",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await api('saveCommonPhrase', {
|
const corpId = doctorInfo.value.corpId;
|
||||||
|
const userId = doctorInfo.value.userid;
|
||||||
|
|
||||||
|
// 如果是默认分类,categoryId 设置为 null
|
||||||
|
let categoryId = editingPhrase.value?.categoryId || currentCategory.value;
|
||||||
|
if (categoryId === DEFAULT_CATEGORY.id) {
|
||||||
|
categoryId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await api("savePersonalPhrase", {
|
||||||
id: editingPhrase.value?.id,
|
id: editingPhrase.value?.id,
|
||||||
categoryId: editingPhrase.value?.categoryId || currentCategory.value,
|
categoryId: categoryId,
|
||||||
content: phraseForm.value.content,
|
content: phraseForm.value.content,
|
||||||
corpId,
|
corpId,
|
||||||
userId
|
userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.code === 200) {
|
console.log("保存常用语返回结果:", result);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
if (editingPhrase.value) {
|
if (editingPhrase.value) {
|
||||||
// 更新
|
// 更新
|
||||||
const index = phrases.value.findIndex(p => p.id === editingPhrase.value.id);
|
const index = phrases.value.findIndex(
|
||||||
|
(p) => p.id === editingPhrase.value.id
|
||||||
|
);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
phrases.value[index].content = phraseForm.value.content;
|
phrases.value[index].content = phraseForm.value.content;
|
||||||
|
// 如果是默认分类,categoryId 设置为 null
|
||||||
|
const newCategoryId = editingPhrase.value.categoryId || currentCategory.value;
|
||||||
|
phrases.value[index].categoryId = newCategoryId === DEFAULT_CATEGORY.id ? null : newCategoryId;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 新增
|
// 新增
|
||||||
|
console.log("新增常用语,返回数据:", result.data);
|
||||||
|
console.log("当前分类ID:", currentCategory.value);
|
||||||
if (result.data) {
|
if (result.data) {
|
||||||
phrases.value.push(result.data);
|
phrases.value.push(result.data);
|
||||||
|
console.log("添加后的 phrases:", phrases.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: editingPhrase.value ? '保存成功' : '添加成功',
|
title: editingPhrase.value ? "保存成功" : "添加成功",
|
||||||
icon: 'success'
|
icon: "success",
|
||||||
});
|
});
|
||||||
|
|
||||||
closePopup();
|
closePopup();
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: result.message || '操作失败',
|
title: result.message || "操作失败",
|
||||||
icon: 'none'
|
icon: "none",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存常用语失败:', error);
|
console.error("保存常用语失败:", error);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '操作失败',
|
title: "操作失败",
|
||||||
icon: 'none'
|
icon: "none",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -289,20 +376,31 @@ const deletePhrase = (phrase) => {
|
|||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
try {
|
try {
|
||||||
await api("deleteCommonPhrase", {
|
const corpId = doctorInfo.value.corpId;
|
||||||
|
const userId = doctorInfo.value.userid;
|
||||||
|
|
||||||
|
const result = await api("deletePersonalPhrase", {
|
||||||
id: phrase.id,
|
id: phrase.id,
|
||||||
corpId: uni.getStorageSync("corpId"),
|
corpId,
|
||||||
|
userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const index = phrases.value.findIndex((p) => p.id === phrase.id);
|
if (result.success) {
|
||||||
if (index !== -1) {
|
const index = phrases.value.findIndex((p) => p.id === phrase.id);
|
||||||
phrases.value.splice(index, 1);
|
if (index !== -1) {
|
||||||
|
phrases.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: "删除成功",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: result.message || "删除失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.showToast({
|
|
||||||
title: "删除成功",
|
|
||||||
icon: "success",
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("删除常用语失败:", error);
|
console.error("删除常用语失败:", error);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
@ -317,12 +415,13 @@ const deletePhrase = (phrase) => {
|
|||||||
|
|
||||||
// 显示添加分类弹窗
|
// 显示添加分类弹窗
|
||||||
const showAddCategoryDialog = () => {
|
const showAddCategoryDialog = () => {
|
||||||
|
editingCategory.value = null;
|
||||||
categoryForm.value.name = "";
|
categoryForm.value.name = "";
|
||||||
showCategoryPopup.value = true;
|
showCategoryPopup.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 保存分类
|
// 保存分类
|
||||||
const saveCategory = () => {
|
const saveCategory = async () => {
|
||||||
if (!categoryForm.value.name.trim()) {
|
if (!categoryForm.value.name.trim()) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: "请输入分类名",
|
title: "请输入分类名",
|
||||||
@ -331,18 +430,66 @@ const saveCategory = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
categories.value.push({
|
if (categoryForm.value.name.length > 6) {
|
||||||
id: `category_${Date.now()}`,
|
uni.showToast({
|
||||||
name: categoryForm.value.name,
|
title: "输入内容超过6个字",
|
||||||
deletable: true, // 用户添加的分类可删除
|
icon: "none",
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
uni.showToast({
|
try {
|
||||||
title: "添加成功",
|
const corpId = doctorInfo.value.corpId;
|
||||||
icon: "success",
|
const userId = doctorInfo.value.userid;
|
||||||
});
|
|
||||||
|
|
||||||
closeCategoryPopup();
|
const result = await api("savePersonalPhraseCategory", {
|
||||||
|
id: editingCategory.value?.id, // 如果是编辑,传入 id
|
||||||
|
name: categoryForm.value.name,
|
||||||
|
corpId,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success && result.data) {
|
||||||
|
if (editingCategory.value) {
|
||||||
|
// 更新分类
|
||||||
|
const index = categories.value.findIndex(
|
||||||
|
(c) => c.id === editingCategory.value.id
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
categories.value[index].name = result.data.name;
|
||||||
|
}
|
||||||
|
uni.showToast({
|
||||||
|
title: "修改成功",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 新增分类
|
||||||
|
categories.value.push({
|
||||||
|
id: result.data.id,
|
||||||
|
name: result.data.name,
|
||||||
|
sort: result.data.sort,
|
||||||
|
deletable: result.data.deletable,
|
||||||
|
});
|
||||||
|
uni.showToast({
|
||||||
|
title: "添加成功",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeCategoryPopup();
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: result.message || "操作失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("保存分类失败:", error);
|
||||||
|
uni.showToast({
|
||||||
|
title: "操作失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 长按分类
|
// 长按分类
|
||||||
@ -361,33 +508,54 @@ const handleCategoryLongPress = (category) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 删除分类
|
// 删除分类
|
||||||
const deleteCategory = (category) => {
|
const deleteCategory = async (category) => {
|
||||||
if (!category.deletable) {
|
if (!category.deletable) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: "默认分类不可删除",
|
title: "该分类不可删除",
|
||||||
icon: "none",
|
icon: "none",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除分类下的所有常用语
|
try {
|
||||||
phrases.value = phrases.value.filter((p) => p.categoryId !== category.id);
|
const corpId = doctorInfo.value.corpId;
|
||||||
|
const userId = doctorInfo.value.userid;
|
||||||
|
|
||||||
// 删除分类
|
const result = await api("deletePersonalPhraseCategory", {
|
||||||
const index = categories.value.findIndex((c) => c.id === category.id);
|
id: category.id,
|
||||||
if (index !== -1) {
|
corpId,
|
||||||
categories.value.splice(index, 1);
|
userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// 删除分类
|
||||||
|
const index = categories.value.findIndex((c) => c.id === category.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
categories.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果删除的是当前分类,切换到默认分类
|
||||||
|
if (currentCategory.value === category.id) {
|
||||||
|
currentCategory.value = DEFAULT_CATEGORY.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: "删除成功",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: result.message || "删除失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("删除分类失败:", error);
|
||||||
|
uni.showToast({
|
||||||
|
title: "删除失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果删除的是当前分类,切换到第一个分类
|
|
||||||
if (currentCategory.value === category.id && categories.value.length > 0) {
|
|
||||||
currentCategory.value = categories.value[0].id;
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showToast({
|
|
||||||
title: "删除成功",
|
|
||||||
icon: "success",
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
@ -402,104 +570,46 @@ const closeCategoryPopup = () => {
|
|||||||
// 加载常用语数据
|
// 加载常用语数据
|
||||||
const loadPhrases = async () => {
|
const loadPhrases = async () => {
|
||||||
try {
|
try {
|
||||||
const corpId = uni.getStorageSync('corpId');
|
const corpId = doctorInfo.value.corpId;
|
||||||
const userId = uni.getStorageSync('userId');
|
const userId = doctorInfo.value.userid;
|
||||||
|
|
||||||
if (!corpId) {
|
const result = await api("getPersonalPhrases", {
|
||||||
uni.showToast({
|
|
||||||
title: '请先登录',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await api('getCommonPhrases', {
|
|
||||||
corpId,
|
corpId,
|
||||||
userId
|
userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.code === 200) {
|
if (result.success && result.data) {
|
||||||
// 更新常用语列表
|
// 更新常用语列表
|
||||||
if (Array.isArray(result.data)) {
|
if (Array.isArray(result.data.phrases)) {
|
||||||
phrases.value = result.data;
|
phrases.value = result.data.phrases;
|
||||||
}
|
}
|
||||||
|
// 更新分类列表
|
||||||
|
if (Array.isArray(result.data.categories)) {
|
||||||
|
categories.value = result.data.categories;
|
||||||
|
|
||||||
// 更新分类列表(合并默认分类和后台分类)
|
// 如果当前分类不存在,保持默认分类
|
||||||
if (Array.isArray(result.categories)) {
|
if (currentCategory.value && currentCategory.value !== DEFAULT_CATEGORY.id) {
|
||||||
const backendCategories = result.categories.map(cat => ({
|
const categoryExists = categories.value.find((c) => c.id === currentCategory.value);
|
||||||
id: cat.id,
|
if (!categoryExists) {
|
||||||
name: cat.name,
|
currentCategory.value = DEFAULT_CATEGORY.id;
|
||||||
deletable: true // 后台分类都可以删除
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 如果后台有分类,使用后台分类,否则使用默认分类
|
|
||||||
if (backendCategories.length > 0) {
|
|
||||||
categories.value = backendCategories;
|
|
||||||
// 设置当前分类为第一个
|
|
||||||
if (!currentCategory.value || !categories.value.find(c => c.id === currentCategory.value)) {
|
|
||||||
currentCategory.value = categories.value[0].id;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: result.message || "加载失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载常用语失败:', error);
|
console.error("加载常用语失败:", error);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '加载失败',
|
title: "加载失败",
|
||||||
icon: 'none'
|
icon: "none",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 模拟数据
|
|
||||||
const getMockPhrases = () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
categoryId: "common",
|
|
||||||
content:
|
|
||||||
"想要买草药自己熬还是配方颗粒直接开水冲着喝?线上购买全国包邮,一二线城市一般1-2天送到,其他城市可能慢一些。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
categoryId: "common",
|
|
||||||
content: "以前得过什么病吗?做过什么检查吗?有检查结果吗?详细说一下。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
categoryId: "common",
|
|
||||||
content: "把我之前给您开的处方,拍个照上传给我看一下,拍招清楚一点。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
categoryId: "common",
|
|
||||||
content: "服药期间要慎炸身体,少吃油炸等辛辣食物,按时作息,精神放松。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
categoryId: "common",
|
|
||||||
content:
|
|
||||||
"煎药最好用砂锅,避免用铁锅,煎药前用冷水浸泡30分钟,水量超过药材2-5厘米,一般中药煎2次,第1次煮沸后再煎20分钟就可以倒出来了,再次加水煮沸后再煎30分钟后倒出,将2次倒出的药混合在一起,就可以按照医嘱用药了。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
categoryId: "common",
|
|
||||||
content:
|
|
||||||
"如果在线上买药,点击整体治疗方案,选择首选处方,划价取药,填写地址后去付款即可。",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
categoryId: "voice",
|
|
||||||
content: "想要买草药自己熬还是配方颗粒直接开水冲着喝?",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 8,
|
|
||||||
categoryId: "visit",
|
|
||||||
content: "您好,我是您的健康管理师,请问有什么可以帮助您的吗?",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 获取系统信息
|
// 获取系统信息
|
||||||
const systemInfo = uni.getSystemInfoSync();
|
const systemInfo = uni.getSystemInfoSync();
|
||||||
@ -600,34 +710,29 @@ $primary-gradient-end: #0877f1;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标题栏
|
// 底部操作栏(页脚)
|
||||||
.action-bar {
|
.footer-action-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
margin: 24rpx 24rpx 0;
|
padding: 24rpx;
|
||||||
.edit-btn {
|
background-color: #fff;
|
||||||
padding: 16rpx 32rpx;
|
border-top: 1px solid #f0f0f0;
|
||||||
.edit-text {
|
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||||
font-size: 28rpx;
|
|
||||||
color: $primary-color;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-phrase-btn {
|
.add-phrase-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 16rpx 24rpx;
|
padding: 20rpx 24rpx;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
135deg,
|
135deg,
|
||||||
$primary-gradient-end 0%,
|
$primary-gradient-end 0%,
|
||||||
$primary-gradient-start 100%
|
$primary-gradient-start 100%
|
||||||
);
|
);
|
||||||
border-radius: 40rpx;
|
border-radius: 30rpx;
|
||||||
box-shadow: 0 4rpx 16rpx rgba(8, 119, 241, 0.2);
|
box-shadow: 0 4rpx 16rpx rgba(8, 119, 241, 0.2);
|
||||||
|
|
||||||
.add-icon {
|
.add-icon {
|
||||||
@ -643,6 +748,19 @@ $primary-gradient-end: #0877f1;
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-btn {
|
||||||
|
padding: 20rpx 32rpx;
|
||||||
|
// background-color: $primary-light;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
// box-shadow: 0 2rpx 8rpx rgba(8, 119, 241, 0.15);
|
||||||
|
|
||||||
|
.edit-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: $primary-color;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
@ -653,17 +771,21 @@ $primary-gradient-end: #0877f1;
|
|||||||
|
|
||||||
// 左侧分类栏
|
// 左侧分类栏
|
||||||
.category-sidebar {
|
.category-sidebar {
|
||||||
width: 180rpx;
|
width: 210rpx;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-right: 1px solid #f0f0f0;
|
border-right: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.category-list {
|
.category-list {
|
||||||
height: 100%;
|
flex: 1;
|
||||||
|
height: 0; // 配合 flex: 1 实现固定高度
|
||||||
|
overflow-y: auto; // 支持垂直滚动
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-item {
|
.category-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 40rpx 16rpx;
|
padding: 20rpx 16rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
@ -690,10 +812,10 @@ $primary-gradient-end: #0877f1;
|
|||||||
|
|
||||||
.delete-badge {
|
.delete-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -12rpx;
|
top: -20rpx;
|
||||||
right: 8rpx;
|
right: 2rpx;
|
||||||
width: 32rpx;
|
width: 28rpx;
|
||||||
height: 32rpx;
|
height: 28rpx;
|
||||||
background-color: #ff3b30;
|
background-color: #ff3b30;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -702,7 +824,7 @@ $primary-gradient-end: #0877f1;
|
|||||||
box-shadow: 0 2rpx 8rpx rgba(255, 59, 48, 0.3);
|
box-shadow: 0 2rpx 8rpx rgba(255, 59, 48, 0.3);
|
||||||
|
|
||||||
.delete-icon {
|
.delete-icon {
|
||||||
font-size: 28rpx;
|
font-size: 24rpx;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
@ -710,27 +832,25 @@ $primary-gradient-end: #0877f1;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-category-item {
|
// 新增分类按钮(固定在底部)
|
||||||
|
.add-category-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 40rpx 16rpx;
|
padding: 40rpx 16rpx;
|
||||||
font-size: 24rpx;
|
|
||||||
color: $primary-color;
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-top: 1px solid #f0f0f0;
|
border-top: 1px solid #f0f0f0;
|
||||||
|
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||||
|
margin-bottom: 18rpx;
|
||||||
|
|
||||||
.plus-icon {
|
.add-category-text {
|
||||||
width: 48rpx;
|
font-size: 26rpx;
|
||||||
height: 48rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 40rpx;
|
|
||||||
font-weight: 300;
|
|
||||||
color: $primary-color;
|
color: $primary-color;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
background-color: $primary-light;
|
background-color: $primary-light;
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -745,7 +865,7 @@ $primary-gradient-end: #0877f1;
|
|||||||
|
|
||||||
.phrases-list {
|
.phrases-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 24rpx;
|
padding: 10rpx;
|
||||||
height: 0; // 配合 flex: 1 实现固定高度
|
height: 0; // 配合 flex: 1 实现固定高度
|
||||||
overflow-y: auto; // 支持垂直滚动
|
overflow-y: auto; // 支持垂直滚动
|
||||||
|
|
||||||
@ -894,7 +1014,7 @@ $primary-gradient-end: #0877f1;
|
|||||||
|
|
||||||
.category-input {
|
.category-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20rpx;
|
padding: 0 20rpx; // 只设置左右 padding,去掉上下 padding
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
@ -903,6 +1023,7 @@ $primary-gradient-end: #0877f1;
|
|||||||
margin-bottom: 32rpx;
|
margin-bottom: 32rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 100rpx;
|
height: 100rpx;
|
||||||
|
line-height: 100rpx; // 行高等于高度,实现垂直居中
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-actions {
|
.popup-actions {
|
||||||
|
|||||||
@ -361,6 +361,20 @@ const goToCommonPhrases = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 跳转到宣教文章页面
|
||||||
|
const goToArticleList = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/message/article-list'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 跳转到问卷列表页面
|
||||||
|
const goToSurveyList = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/message/survey-list'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const morePanelButtons = [
|
const morePanelButtons = [
|
||||||
{ text: "照片", icon: "/static/icon/zhaopian.png", action: showImagePicker },
|
{ text: "照片", icon: "/static/icon/zhaopian.png", action: showImagePicker },
|
||||||
{
|
{
|
||||||
@ -376,12 +390,12 @@ const morePanelButtons = [
|
|||||||
{
|
{
|
||||||
text: "宣教",
|
text: "宣教",
|
||||||
icon: "/static/icon/xuanjiaowenzhang.png",
|
icon: "/static/icon/xuanjiaowenzhang.png",
|
||||||
action: showImagePicker,
|
action: goToArticleList,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "问卷",
|
text: "问卷",
|
||||||
icon: "/static/icon/zhaopian.png",
|
icon: "/static/icon/wenjuan.png",
|
||||||
action: showImagePicker,
|
action: goToSurveyList,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "结束问诊",
|
text: "结束问诊",
|
||||||
|
|||||||
88
pages/message/components/consult-accept.vue
Normal file
88
pages/message/components/consult-accept.vue
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<view class="consult-accept-container">
|
||||||
|
<view class="accept-card">
|
||||||
|
<view class="accept-content">
|
||||||
|
<text class="accept-text">患者已发起咨询申请,请及时接诊</text>
|
||||||
|
</view>
|
||||||
|
<view class="accept-actions">
|
||||||
|
<button class="btn-cancel" @click="handleReject">暂不接受</button>
|
||||||
|
<button class="btn-confirm" @click="handleAccept">接受咨询</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { defineEmits } from 'vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(['accept', 'reject']);
|
||||||
|
|
||||||
|
const handleAccept = () => {
|
||||||
|
emit('accept');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReject = () => {
|
||||||
|
emit('reject');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.consult-accept-container {
|
||||||
|
width: 100%;
|
||||||
|
padding: 20rpx 32rpx;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accept-card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 32rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accept-content {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accept-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accept-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel,
|
||||||
|
.btn-confirm {
|
||||||
|
flex: 1;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm {
|
||||||
|
background-color: #1677ff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -63,8 +63,46 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 自定义消息卡片 -->
|
<!-- 自定义消息卡片 -->
|
||||||
<!-- <template v-else-if="message.type === 'TIMCustomElem'">
|
<template v-else-if="message.type === 'TIMCustomElem'">
|
||||||
|
<!-- 文章消息 -->
|
||||||
<view
|
<view
|
||||||
|
v-if="getCustomMessageType(message) === 'article'"
|
||||||
|
class="article-card"
|
||||||
|
@click="handleArticleClick(message)"
|
||||||
|
>
|
||||||
|
<view class="article-content">
|
||||||
|
<view class="article-title">{{ getArticleData(message).title }}</view>
|
||||||
|
<view class="article-desc">{{ getArticleData(message).desc }}</view>
|
||||||
|
</view>
|
||||||
|
<image
|
||||||
|
v-if="getArticleData(message).imgUrl"
|
||||||
|
class="article-image"
|
||||||
|
:src="getArticleData(message).imgUrl"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 问卷消息 -->
|
||||||
|
<view
|
||||||
|
v-else-if="getCustomMessageType(message) === 'survey'"
|
||||||
|
class="survey-card"
|
||||||
|
@click="handleSurveyClick(message)"
|
||||||
|
>
|
||||||
|
<view class="survey-content">
|
||||||
|
<view class="survey-title">{{ getSurveyData(message).title }}</view>
|
||||||
|
<view class="survey-desc">{{ getSurveyData(message).desc }}</view>
|
||||||
|
</view>
|
||||||
|
<image
|
||||||
|
v-if="getSurveyData(message).imgUrl"
|
||||||
|
class="survey-image"
|
||||||
|
:src="getSurveyData(message).imgUrl"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 其他自定义消息 -->
|
||||||
|
<!-- <view
|
||||||
|
v-else
|
||||||
class="card-avatar-row"
|
class="card-avatar-row"
|
||||||
@click="() => console.log('点击头像', message)"
|
@click="() => console.log('点击头像', message)"
|
||||||
>
|
>
|
||||||
@ -74,8 +112,8 @@
|
|||||||
:flow="message.flow"
|
:flow="message.flow"
|
||||||
@viewDetail="$emit('viewDetail', $event)"
|
@viewDetail="$emit('viewDetail', $event)"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view> -->
|
||||||
</template> -->
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -163,6 +201,90 @@ const getVoiceStyle = (duration) => {
|
|||||||
width: width + "rpx",
|
width: width + "rpx",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取自定义消息类型
|
||||||
|
const getCustomMessageType = (message) => {
|
||||||
|
try {
|
||||||
|
if (message.payload && message.payload.data) {
|
||||||
|
const data = JSON.parse(message.payload.data);
|
||||||
|
return data.type || '';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析自定义消息失败:', error);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取文章数据
|
||||||
|
const getArticleData = (message) => {
|
||||||
|
try {
|
||||||
|
if (message.payload && message.payload.data) {
|
||||||
|
const data = JSON.parse(message.payload.data);
|
||||||
|
return {
|
||||||
|
title: data.title || '宣教文章',
|
||||||
|
desc: data.desc || '宣教文章',
|
||||||
|
url: data.url || '',
|
||||||
|
imgUrl: data.imgUrl || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析文章数据失败:', error);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: '宣教文章',
|
||||||
|
desc: '宣教文章',
|
||||||
|
url: '',
|
||||||
|
imgUrl: ''
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理文章点击
|
||||||
|
const handleArticleClick = (message) => {
|
||||||
|
const articleData = getArticleData(message);
|
||||||
|
if (articleData.url) {
|
||||||
|
// 跳转到文章详情页或打开外部链接
|
||||||
|
// 这里可以根据实际需求处理
|
||||||
|
console.log('打开文章:', articleData.url);
|
||||||
|
// uni.navigateTo({
|
||||||
|
// url: `/pages/article/detail?url=${encodeURIComponent(articleData.url)}`
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取问卷数据
|
||||||
|
const getSurveyData = (message) => {
|
||||||
|
try {
|
||||||
|
if (message.payload && message.payload.data) {
|
||||||
|
const data = JSON.parse(message.payload.data);
|
||||||
|
return {
|
||||||
|
title: data.title || '填写问卷',
|
||||||
|
desc: data.desc || '请填写问卷',
|
||||||
|
url: data.url || '',
|
||||||
|
imgUrl: data.imgUrl || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析问卷数据失败:', error);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: '填写问卷',
|
||||||
|
desc: '请填写问卷',
|
||||||
|
url: '',
|
||||||
|
imgUrl: ''
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理问卷点击
|
||||||
|
const handleSurveyClick = (message) => {
|
||||||
|
const surveyData = getSurveyData(message);
|
||||||
|
if (surveyData.url) {
|
||||||
|
// 跳转到问卷填写页面或打开外部链接
|
||||||
|
console.log('打开问卷:', surveyData.url);
|
||||||
|
// uni.navigateTo({
|
||||||
|
// url: `/pages/survey/fill?url=${encodeURIComponent(surveyData.url)}`
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
297
pages/message/components/reject-reason-modal.vue
Normal file
297
pages/message/components/reject-reason-modal.vue
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
<template>
|
||||||
|
<view v-if="visible" class="modal-overlay" @click="handleCancel">
|
||||||
|
<view class="modal-container" @click.stop>
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">选择暂不接受咨询原因,供患者知晓</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="modal-content">
|
||||||
|
<!-- 预设原因选项 -->
|
||||||
|
<view class="reason-options">
|
||||||
|
<view
|
||||||
|
v-for="(option, index) in reasonOptions"
|
||||||
|
:key="index"
|
||||||
|
class="reason-option"
|
||||||
|
:class="{ active: selectedReason === option }"
|
||||||
|
@click="selectReason(option)"
|
||||||
|
>
|
||||||
|
<text class="option-text">{{ option }}</text>
|
||||||
|
<view class="option-icon" :class="{ checked: selectedReason === option }">
|
||||||
|
<text v-if="selectedReason === option" class="check-mark">✓</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 自定义输入选项 -->
|
||||||
|
<view
|
||||||
|
class="reason-option custom-option"
|
||||||
|
:class="{ active: isCustomInput }"
|
||||||
|
@click="selectCustomInput"
|
||||||
|
>
|
||||||
|
<text class="option-text">填写拒诊理由</text>
|
||||||
|
<view class="option-icon arrow">
|
||||||
|
<text class="arrow-icon">›</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 自定义输入框 -->
|
||||||
|
<view v-if="isCustomInput" class="custom-input-container">
|
||||||
|
<textarea
|
||||||
|
class="custom-textarea"
|
||||||
|
v-model="customReason"
|
||||||
|
placeholder="请输入理由,供患者知晓"
|
||||||
|
maxlength="200"
|
||||||
|
:auto-height="true"
|
||||||
|
/>
|
||||||
|
<text class="char-count">{{ customReason.length }}/200</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="modal-footer">
|
||||||
|
<button class="btn-cancel" @click="handleCancel">取消</button>
|
||||||
|
<button class="btn-confirm" @click="handleConfirm">确定</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['confirm', 'cancel']);
|
||||||
|
|
||||||
|
// 预设的拒绝原因选项
|
||||||
|
const reasonOptions = ref([
|
||||||
|
'临时有紧急事务',
|
||||||
|
'患者病情复杂,需线下就诊',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const selectedReason = ref('');
|
||||||
|
const isCustomInput = ref(false);
|
||||||
|
const customReason = ref('');
|
||||||
|
|
||||||
|
// 选择预设原因
|
||||||
|
const selectReason = (option) => {
|
||||||
|
selectedReason.value = option;
|
||||||
|
isCustomInput.value = false;
|
||||||
|
customReason.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择自定义输入
|
||||||
|
const selectCustomInput = () => {
|
||||||
|
selectedReason.value = '';
|
||||||
|
isCustomInput.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消
|
||||||
|
const handleCancel = () => {
|
||||||
|
// 重置状态
|
||||||
|
selectedReason.value = '';
|
||||||
|
isCustomInput.value = false;
|
||||||
|
customReason.value = '';
|
||||||
|
emit('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确定
|
||||||
|
const handleConfirm = () => {
|
||||||
|
let reason = '';
|
||||||
|
|
||||||
|
if (isCustomInput.value) {
|
||||||
|
reason = customReason.value.trim();
|
||||||
|
if (!reason) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入拒绝原因',
|
||||||
|
icon: 'none',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reason = selectedReason.value;
|
||||||
|
if (!reason) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请选择拒绝原因',
|
||||||
|
icon: 'none',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('confirm', reason);
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
selectedReason.value = '';
|
||||||
|
isCustomInput.value = false;
|
||||||
|
customReason.value = '';
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
width: 600rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 32rpx 32rpx 24rpx;
|
||||||
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
padding: 24rpx 32rpx;
|
||||||
|
max-height: 600rpx;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reason-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reason-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 24rpx;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
border: 2rpx solid transparent;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #e6f4ff;
|
||||||
|
border-color: #1677ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2rpx solid #d9d9d9;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
background-color: #1677ff;
|
||||||
|
border-color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.arrow {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-mark {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-icon {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #999;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-input-container {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
padding: 16rpx;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 120rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char-count {
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 24rpx;
|
||||||
|
padding: 24rpx 32rpx 32rpx;
|
||||||
|
border-top: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel,
|
||||||
|
.btn-confirm {
|
||||||
|
flex: 1;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm {
|
||||||
|
background-color: #1677ff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<view v-if="notifyText" class="notify-bar">
|
<!-- <view v-if="notifyText" class="notify-bar">
|
||||||
<view class="notify-line"></view>
|
<view class="notify-line"></view>
|
||||||
<view class="notify-text">{{ notifyText }}</view>
|
<view class="notify-text">{{ notifyText }}</view>
|
||||||
<view class="notify-line"></view>
|
<view class="notify-line"></view>
|
||||||
</view>
|
</view> -->
|
||||||
<view class="system-message">
|
<view class="system-message">
|
||||||
<view class="system-text">{{ text }}</view>
|
<view class="system-text">{{ text }}</view>
|
||||||
</view>
|
</view>
|
||||||
@ -21,17 +21,111 @@ const props = defineProps({
|
|||||||
|
|
||||||
const payload = computed(() => props.message?.payload || {});
|
const payload = computed(() => props.message?.payload || {});
|
||||||
|
|
||||||
const extension = computed(() => {
|
// 解析系统消息内容
|
||||||
|
const systemMessageData = computed(() => {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(payload.value.extension);
|
// 尝试从 payload.data 解析系统消息
|
||||||
|
if (payload.value.data) {
|
||||||
|
const data = typeof payload.value.data === 'string'
|
||||||
|
? JSON.parse(payload.value.data)
|
||||||
|
: payload.value.data;
|
||||||
|
|
||||||
|
if (data.type === 'system_message') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return {};
|
console.error('解析系统消息失败:', e);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const text = computed(() => extension.value.patient || payload.value.data || '')
|
// 解析扩展信息
|
||||||
|
const extension = computed(() => {
|
||||||
|
try {
|
||||||
|
if (payload.value.extension) {
|
||||||
|
return typeof payload.value.extension === 'string'
|
||||||
|
? JSON.parse(payload.value.extension)
|
||||||
|
: payload.value.extension;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析扩展信息失败:', e);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
const notifyText = computed(() => extension.value.notifyText || '')
|
// 显示的文本内容
|
||||||
|
const text = computed(() => {
|
||||||
|
// 优先从系统消息数据中获取文本
|
||||||
|
if (systemMessageData.value?.text) {
|
||||||
|
return systemMessageData.value.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据消息类型返回默认文本
|
||||||
|
if (systemMessageData.value?.messageType) {
|
||||||
|
const messageType = systemMessageData.value.messageType;
|
||||||
|
switch (messageType) {
|
||||||
|
case 'consult_pending':
|
||||||
|
return '患者已发起咨询申请,请及时接诊';
|
||||||
|
case 'consult_accepted':
|
||||||
|
return '医生已接诊';
|
||||||
|
case 'consult_rejected':
|
||||||
|
return '医生暂时无法接诊';
|
||||||
|
case 'consult_ended':
|
||||||
|
return '问诊已结束';
|
||||||
|
case 'consult_timeout':
|
||||||
|
return '问诊已超时';
|
||||||
|
default:
|
||||||
|
return systemMessageData.value.content || '[系统消息]';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧格式:从 extension 中获取
|
||||||
|
if (extension.value.patient) {
|
||||||
|
return extension.value.patient;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧格式:直接从 payload.data 获取
|
||||||
|
if (payload.value.data && typeof payload.value.data === 'string') {
|
||||||
|
// 如果 data 不是 JSON 格式,直接显示
|
||||||
|
try {
|
||||||
|
JSON.parse(payload.value.data);
|
||||||
|
} catch {
|
||||||
|
return payload.value.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '[系统消息]';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通知文本(红色提示)
|
||||||
|
const notifyText = computed(() => {
|
||||||
|
// 从扩展信息中获取通知文本
|
||||||
|
if (extension.value.notifyText) {
|
||||||
|
return extension.value.notifyText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据系统消息类型显示不同的通知
|
||||||
|
if (systemMessageData.value) {
|
||||||
|
const messageType = systemMessageData.value.messageType;
|
||||||
|
switch (messageType) {
|
||||||
|
case 'consult_pending':
|
||||||
|
return '待接诊';
|
||||||
|
case 'consult_rejected':
|
||||||
|
return '已拒绝';
|
||||||
|
case 'consult_timeout':
|
||||||
|
return '已超时';
|
||||||
|
case 'consult_accepted':
|
||||||
|
return '已接诊';
|
||||||
|
case 'consult_ended':
|
||||||
|
return '已结束';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -54,7 +54,9 @@
|
|||||||
<image
|
<image
|
||||||
v-if="message.flow === 'in'"
|
v-if="message.flow === 'in'"
|
||||||
class="doctor-msg-avatar"
|
class="doctor-msg-avatar"
|
||||||
:src="chatMember[message.from]?.avatar || '/static/default-avatar.png'"
|
:src="
|
||||||
|
chatMember[message.from]?.avatar || '/static/default-avatar.png'
|
||||||
|
"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -62,7 +64,9 @@
|
|||||||
<image
|
<image
|
||||||
v-if="message.flow === 'out'"
|
v-if="message.flow === 'out'"
|
||||||
class="user-msg-avatar"
|
class="user-msg-avatar"
|
||||||
:src="chatMember[message.from]?.avatar || '/static/home/avatar.svg'"
|
:src="
|
||||||
|
chatMember[message.from]?.avatar || '/static/home/avatar.svg'
|
||||||
|
"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -108,9 +112,23 @@
|
|||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 接受问诊组件 -->
|
||||||
|
<ConsultAccept
|
||||||
|
v-if="showConsultAccept"
|
||||||
|
@accept="handleAcceptConsult"
|
||||||
|
@reject="handleRejectConsult"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 拒绝原因对话框 -->
|
||||||
|
<RejectReasonModal
|
||||||
|
:visible="showRejectReasonModal"
|
||||||
|
@confirm="handleRejectReasonConfirm"
|
||||||
|
@cancel="handleRejectReasonCancel"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 聊天输入组件 -->
|
<!-- 聊天输入组件 -->
|
||||||
<ChatInput
|
<ChatInput
|
||||||
v-if="!isEvaluationPopupOpen"
|
v-if="!isEvaluationPopupOpen && !showConsultAccept"
|
||||||
ref="chatInputRef"
|
ref="chatInputRef"
|
||||||
:timChatManager="timChatManager"
|
:timChatManager="timChatManager"
|
||||||
:formatTime="formatTime"
|
:formatTime="formatTime"
|
||||||
@ -143,10 +161,13 @@ import {
|
|||||||
handleViewDetail,
|
handleViewDetail,
|
||||||
checkIMConnectionStatus,
|
checkIMConnectionStatus,
|
||||||
} from "@/utils/chat-utils.js";
|
} from "@/utils/chat-utils.js";
|
||||||
|
import { sendConsultRejectedMessage } 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";
|
||||||
import SystemMessage from "./components/system-message.vue";
|
import SystemMessage from "./components/system-message.vue";
|
||||||
|
import ConsultAccept from "./components/consult-accept.vue";
|
||||||
|
import RejectReasonModal from "./components/reject-reason-modal.vue";
|
||||||
|
|
||||||
const timChatManager = globalTimChatManager;
|
const timChatManager = globalTimChatManager;
|
||||||
|
|
||||||
@ -158,15 +179,12 @@ const { initIMAfterLogin } = useAccountStore();
|
|||||||
const chatInputRef = ref(null);
|
const chatInputRef = ref(null);
|
||||||
|
|
||||||
const groupId = ref("");
|
const groupId = ref("");
|
||||||
const {
|
const { chatMember, getGroupInfo } = useGroupChat(groupId);
|
||||||
chatMember,
|
|
||||||
getGroupInfo
|
|
||||||
} = useGroupChat(groupId);
|
|
||||||
|
|
||||||
// 动态设置导航栏标题
|
// 动态设置导航栏标题
|
||||||
const updateNavigationTitle = () => {
|
const updateNavigationTitle = () => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: "群聊"
|
title: "群聊",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -180,6 +198,12 @@ const chatInfo = ref({
|
|||||||
// 评价弹窗状态
|
// 评价弹窗状态
|
||||||
const isEvaluationPopupOpen = ref(false);
|
const isEvaluationPopupOpen = ref(false);
|
||||||
|
|
||||||
|
// 接受问诊状态
|
||||||
|
const showConsultAccept = ref(false);
|
||||||
|
|
||||||
|
// 拒绝原因对话框状态
|
||||||
|
const showRejectReasonModal = ref(false);
|
||||||
|
|
||||||
// 消息列表相关状态
|
// 消息列表相关状态
|
||||||
const messageList = ref([]);
|
const messageList = ref([]);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
@ -192,10 +216,73 @@ const lastFirstMessageId = ref("");
|
|||||||
|
|
||||||
// 判断是否为系统消息
|
// 判断是否为系统消息
|
||||||
function isSystemMessage(message) {
|
function isSystemMessage(message) {
|
||||||
const description = message.payload?.description;
|
if (message.type !== "TIMCustomElem") {
|
||||||
return (
|
return false;
|
||||||
message.type === "TIMCustomElem" && description === "SYSTEM_NOTIFICATION"
|
}
|
||||||
);
|
|
||||||
|
try {
|
||||||
|
// 检查 payload.data 是否包含系统消息标记
|
||||||
|
if (message.payload?.data) {
|
||||||
|
const data = typeof message.payload.data === 'string'
|
||||||
|
? JSON.parse(message.payload.data)
|
||||||
|
: message.payload.data;
|
||||||
|
|
||||||
|
// 检查是否为系统消息类型
|
||||||
|
if (data.type === 'system_message') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 description 是否为系统消息标记
|
||||||
|
if (message.payload?.description === '系统消息标记') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧的系统消息格式
|
||||||
|
if (message.payload?.description === 'SYSTEM_NOTIFICATION') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('判断系统消息失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有待接诊的系统消息
|
||||||
|
function checkConsultPendingStatus() {
|
||||||
|
// 查找最新的系统消息
|
||||||
|
for (let i = messageList.value.length - 1; i >= 0; i--) {
|
||||||
|
const message = messageList.value[i];
|
||||||
|
|
||||||
|
if (message.type === "TIMCustomElem" && message.payload?.data) {
|
||||||
|
try {
|
||||||
|
const data = typeof message.payload.data === 'string'
|
||||||
|
? JSON.parse(message.payload.data)
|
||||||
|
: message.payload.data;
|
||||||
|
|
||||||
|
// 如果是 consult_pending 类型的系统消息,显示接受组件
|
||||||
|
if (data.type === 'system_message' && data.messageType === 'consult_pending') {
|
||||||
|
showConsultAccept.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是其他系统消息类型(如已接诊、已结束等),隐藏接受组件
|
||||||
|
if (data.type === 'system_message' &&
|
||||||
|
(data.messageType === 'consult_accepted' ||
|
||||||
|
data.messageType === 'consult_ended' ||
|
||||||
|
data.messageType === 'consult_rejected')) {
|
||||||
|
showConsultAccept.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析系统消息失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有找到相关系统消息,隐藏接受组件
|
||||||
|
showConsultAccept.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取消息气泡样式类
|
// 获取消息气泡样式类
|
||||||
@ -204,7 +291,6 @@ function getBubbleClass(message) {
|
|||||||
if (message.type === "TIMImageElem") {
|
if (message.type === "TIMImageElem") {
|
||||||
return "image-bubble";
|
return "image-bubble";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === "TIMCustomElem") {
|
if (message.type === "TIMCustomElem") {
|
||||||
return message.flow === "out" ? "user-bubble" : "doctor-bubble-blue";
|
return message.flow === "out" ? "user-bubble" : "doctor-bubble-blue";
|
||||||
}
|
}
|
||||||
@ -288,6 +374,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();
|
||||||
|
|
||||||
// 立即滚动到底部,不使用延迟
|
// 立即滚动到底部,不使用延迟
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
scrollToBottom(true);
|
scrollToBottom(true);
|
||||||
@ -346,6 +436,9 @@ const initTIMCallbacks = async () => {
|
|||||||
isCompleted.value = data.isCompleted || false;
|
isCompleted.value = data.isCompleted || false;
|
||||||
isLoadingMore.value = false;
|
isLoadingMore.value = false;
|
||||||
|
|
||||||
|
// 检查是否有待接诊的系统消息
|
||||||
|
checkConsultPendingStatus();
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (data.isRefresh) {
|
if (data.isRefresh) {
|
||||||
console.log("后台刷新完成,保持当前滚动位置");
|
console.log("后台刷新完成,保持当前滚动位置");
|
||||||
@ -488,13 +581,13 @@ const scrollToBottom = (immediate = false) => {
|
|||||||
|
|
||||||
if (immediate) {
|
if (immediate) {
|
||||||
// 立即滚动:先清空再设置,触发滚动
|
// 立即滚动:先清空再设置,触发滚动
|
||||||
scrollIntoView.value = '';
|
scrollIntoView.value = "";
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
scrollIntoView.value = targetId;
|
scrollIntoView.value = targetId;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 正常滚动,使用短延迟确保DOM更新
|
// 正常滚动,使用短延迟确保DOM更新
|
||||||
scrollIntoView.value = '';
|
scrollIntoView.value = "";
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
scrollIntoView.value = targetId;
|
scrollIntoView.value = targetId;
|
||||||
}, 50);
|
}, 50);
|
||||||
@ -603,9 +696,110 @@ const sendCommonPhrase = (content) => {
|
|||||||
|
|
||||||
// 暴露方法给常用语页面调用
|
// 暴露方法给常用语页面调用
|
||||||
defineExpose({
|
defineExpose({
|
||||||
sendCommonPhrase
|
sendCommonPhrase,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 处理接受问诊
|
||||||
|
const handleAcceptConsult = async () => {
|
||||||
|
try {
|
||||||
|
uni.showLoading({
|
||||||
|
title: '处理中...',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送接受问诊的系统消息
|
||||||
|
const customMessage = {
|
||||||
|
data: JSON.stringify({
|
||||||
|
type: 'system_message',
|
||||||
|
messageType: 'consult_accepted',
|
||||||
|
content: '医生已接诊',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}),
|
||||||
|
description: '系统消息标记',
|
||||||
|
extension: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const message = timChatManager.tim.createCustomMessage({
|
||||||
|
to: chatInfo.value.conversationID.replace('GROUP', ''),
|
||||||
|
conversationType: timChatManager.TIM.TYPES.CONV_GROUP,
|
||||||
|
payload: customMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendResult = await timChatManager.tim.sendMessage(message);
|
||||||
|
|
||||||
|
if (sendResult.code === 0) {
|
||||||
|
showConsultAccept.value = false;
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: '已接受问诊',
|
||||||
|
icon: 'success',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(sendResult.message || '发送失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('接受问诊失败:', error);
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '操作失败',
|
||||||
|
icon: 'none',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理拒绝问诊
|
||||||
|
const handleRejectConsult = () => {
|
||||||
|
// 显示拒绝原因选择对话框
|
||||||
|
showRejectReasonModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理拒绝原因确认
|
||||||
|
const handleRejectReasonConfirm = async (reason) => {
|
||||||
|
try {
|
||||||
|
showRejectReasonModal.value = false;
|
||||||
|
|
||||||
|
uni.showLoading({
|
||||||
|
title: '处理中...',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取医生信息
|
||||||
|
const memberName = account.value?.name || '医生';
|
||||||
|
|
||||||
|
// 获取群组ID
|
||||||
|
const groupId = chatInfo.value.conversationID.replace('GROUP', '');
|
||||||
|
|
||||||
|
// 调用后端接口发送拒绝消息
|
||||||
|
const result = await sendConsultRejectedMessage({
|
||||||
|
groupId,
|
||||||
|
memberName,
|
||||||
|
reason,
|
||||||
|
});
|
||||||
|
|
||||||
|
uni.hideLoading();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showConsultAccept.value = false;
|
||||||
|
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',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理拒绝原因取消
|
||||||
|
const handleRejectReasonCancel = () => {
|
||||||
|
showRejectReasonModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
// 页面卸载
|
// 页面卸载
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
clearMessageCache();
|
clearMessageCache();
|
||||||
@ -615,6 +809,172 @@ onUnmounted(() => {
|
|||||||
timChatManager.setCallback("onMessageReceived", null);
|
timChatManager.setCallback("onMessageReceived", null);
|
||||||
timChatManager.setCallback("onMessageListLoaded", null);
|
timChatManager.setCallback("onMessageListLoaded", null);
|
||||||
timChatManager.setCallback("onError", null);
|
timChatManager.setCallback("onError", null);
|
||||||
|
|
||||||
|
// 移除文章发送监听
|
||||||
|
uni.$off("sendArticle");
|
||||||
|
// 移除问卷发送监听
|
||||||
|
uni.$off("sendSurvey");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听文章发送事件
|
||||||
|
uni.$on("sendArticle", async (data) => {
|
||||||
|
const { article, corpId, userId } = data;
|
||||||
|
|
||||||
|
if (!article || !article._id) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "文章信息不完整",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取环境变量
|
||||||
|
const env = __VITE_ENV__;
|
||||||
|
const baseUrl = env.VITE_PATIENT_PAGE_BASE_URL || "";
|
||||||
|
|
||||||
|
// 构建文章链接
|
||||||
|
const articleUrl = `${baseUrl}pages/article/index?id=${article._id}&corpId=${corpId}`;
|
||||||
|
|
||||||
|
// 创建自定义消息
|
||||||
|
const customMessage = {
|
||||||
|
data: JSON.stringify({
|
||||||
|
type: "article",
|
||||||
|
title: article.title || "宣教文章",
|
||||||
|
desc: "宣教文章",
|
||||||
|
url: articleUrl,
|
||||||
|
imgUrl:
|
||||||
|
"https://796f-youcan-clouddev-1-8ewcqf31dbb2b5-1317294507.tcb.qcloud.la/other/19-%E9%97%AE%E5%8D%B7.png?sign=55a4cd77c418b2c548b65792a2cf6bce&t=1701328694",
|
||||||
|
}),
|
||||||
|
description: "ARTICLE",
|
||||||
|
extension: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 发送自定义消息
|
||||||
|
const message = timChatManager.tim.createCustomMessage({
|
||||||
|
to: chatInfo.value.conversationID.replace("GROUP", ""),
|
||||||
|
conversationType: timChatManager.TIM.TYPES.CONV_GROUP,
|
||||||
|
payload: customMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendResult = await timChatManager.tim.sendMessage(message);
|
||||||
|
|
||||||
|
if (sendResult.code === 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "发送成功",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 记录发送记录(可选)
|
||||||
|
// await addArticleSendRecord({
|
||||||
|
// corpId,
|
||||||
|
// userId,
|
||||||
|
// articleId: article._id,
|
||||||
|
// customerId: chatInfo.value.userID
|
||||||
|
// });
|
||||||
|
} else {
|
||||||
|
throw new Error(sendResult.message || "发送失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("发送文章失败:", error);
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || "发送失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听问卷发送事件
|
||||||
|
uni.$on("sendSurvey", async (data) => {
|
||||||
|
const { survey, corpId, userId, sendSurveyId } = data;
|
||||||
|
|
||||||
|
if (!survey || !survey._id) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "问卷信息不完整",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取环境变量
|
||||||
|
const env = __VITE_ENV__;
|
||||||
|
const baseUrl = env.VITE_PATIENT_PAGE_BASE_URL || "";
|
||||||
|
const surveyUrl = env.VITE_SURVEY_URL || "";
|
||||||
|
|
||||||
|
// 获取客户信息
|
||||||
|
const customerId = chatInfo.value.userID || "";
|
||||||
|
const customerName = chatInfo.value.customerName || "";
|
||||||
|
|
||||||
|
// 创建问卷记录
|
||||||
|
const { createSurveyRecord } = await import("@/utils/api.js");
|
||||||
|
const recordRes = await createSurveyRecord({
|
||||||
|
corpId,
|
||||||
|
userId,
|
||||||
|
surveryId: survey._id,
|
||||||
|
memberId: customerId,
|
||||||
|
customer: customerName,
|
||||||
|
sendSurveyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!recordRes.success) {
|
||||||
|
throw new Error(recordRes.message || "创建问卷记录失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
const answerId = recordRes.data?.id || "";
|
||||||
|
|
||||||
|
// 构建问卷链接
|
||||||
|
let surveyLink = "";
|
||||||
|
if (survey.createBy === "system") {
|
||||||
|
// 系统问卷
|
||||||
|
surveyLink = `${surveyUrl}?corpId=${corpId}&surveyId=${survey.surveyId}&memberId=${customerId}&sendSurveyId=${sendSurveyId}&userId=${userId}`;
|
||||||
|
} else {
|
||||||
|
// 自定义问卷
|
||||||
|
surveyLink = `${baseUrl}pages/survery/fill?corpId=${corpId}&surveryId=${
|
||||||
|
survey._id
|
||||||
|
}&memberId=${customerId}&answerId=${answerId}&name=${encodeURIComponent(
|
||||||
|
customerName
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建自定义消息
|
||||||
|
const customMessage = {
|
||||||
|
data: JSON.stringify({
|
||||||
|
type: "survey",
|
||||||
|
title: survey.name || "填写问卷",
|
||||||
|
desc: "请填写问卷",
|
||||||
|
url: surveyLink,
|
||||||
|
imgUrl:
|
||||||
|
"https://796f-youcan-clouddev-1-8ewcqf31dbb2b5-1317294507.tcb.qcloud.la/other/19-%E9%97%AE%E5%8D%B7.png?sign=55a4cd77c418b2c548b65792a2cf6bce&t=1701328694",
|
||||||
|
}),
|
||||||
|
description: "SURVEY",
|
||||||
|
extension: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 发送自定义消息
|
||||||
|
const message = timChatManager.tim.createCustomMessage({
|
||||||
|
to: chatInfo.value.conversationID.replace("GROUP", ""),
|
||||||
|
conversationType: timChatManager.TIM.TYPES.CONV_GROUP,
|
||||||
|
payload: customMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendResult = await timChatManager.tim.sendMessage(message);
|
||||||
|
|
||||||
|
if (sendResult.code === 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "发送成功",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(sendResult.message || "发送失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("发送问卷失败:", error);
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || "发送失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
423
pages/message/survey-list.vue
Normal file
423
pages/message/survey-list.vue
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
<template>
|
||||||
|
<view class="survey-page">
|
||||||
|
<view class="header">
|
||||||
|
<view class="search-bar">
|
||||||
|
<uni-icons type="search" size="18" color="#999" />
|
||||||
|
<input class="search-input" v-model="searchName" placeholder="输入问卷名称搜索" @input="handleSearch" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="content">
|
||||||
|
<view class="category-sidebar">
|
||||||
|
<scroll-view scroll-y class="category-scroll">
|
||||||
|
<view
|
||||||
|
v-for="cate in categoryList"
|
||||||
|
:key="cate._id || 'all'"
|
||||||
|
class="category-item"
|
||||||
|
:class="{ active: currentCateId === (cate._id || 'all') }"
|
||||||
|
@click="selectCategory(cate)"
|
||||||
|
>
|
||||||
|
{{ cate.name }}
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="survey-list">
|
||||||
|
<scroll-view
|
||||||
|
scroll-y
|
||||||
|
class="survey-scroll"
|
||||||
|
@scrolltolower="loadMore"
|
||||||
|
lower-threshold="50"
|
||||||
|
>
|
||||||
|
<view v-if="loading && surveyList.length === 0" class="loading-container">
|
||||||
|
<uni-icons type="spinner-cycle" size="30" color="#999" />
|
||||||
|
<text class="loading-text">加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else-if="surveyList.length === 0" class="empty-container">
|
||||||
|
<empty-data :title="emptyText || '暂无问卷'" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else>
|
||||||
|
<view
|
||||||
|
v-for="survey in surveyList"
|
||||||
|
:key="survey._id"
|
||||||
|
class="survey-item"
|
||||||
|
>
|
||||||
|
<view class="survey-content" @click="previewSurvey(survey)">
|
||||||
|
<text class="survey-title">{{ survey.name }}</text>
|
||||||
|
<text class="survey-desc">{{ survey.description || '暂无问卷说明' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="survey-action">
|
||||||
|
<button class="send-btn" size="mini" type="primary" @click="sendSurvey(survey)">
|
||||||
|
发送
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="loading && surveyList.length > 0" class="loading-more">
|
||||||
|
<uni-icons type="spinner-cycle" size="20" color="#999" />
|
||||||
|
<text>加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="!loading && surveyList.length >= total" class="no-more">
|
||||||
|
没有更多了
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="footer">
|
||||||
|
<button class="cancel-btn" @click="goBack">取消</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { getSurveyCateList, getSurveyList, createSurveyRecord } from '@/utils/api.js';
|
||||||
|
import useAccountStore from '@/store/account.js';
|
||||||
|
import EmptyData from '@/components/empty-data.vue';
|
||||||
|
|
||||||
|
const env = __VITE_ENV__;
|
||||||
|
const accountStore = useAccountStore();
|
||||||
|
const corpId = env.MP_CORP_ID;
|
||||||
|
const userId = ref('');
|
||||||
|
|
||||||
|
// 搜索关键词
|
||||||
|
const searchName = ref('');
|
||||||
|
let searchTimer = null;
|
||||||
|
|
||||||
|
// 分类列表
|
||||||
|
const categoryList = ref([{ _id: '', name: '全部' }]);
|
||||||
|
const currentCateId = ref('');
|
||||||
|
|
||||||
|
// 问卷列表
|
||||||
|
const surveyList = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const page = ref(1);
|
||||||
|
const pageSize = 30;
|
||||||
|
const total = ref(0);
|
||||||
|
const emptyText = ref('');
|
||||||
|
|
||||||
|
// 获取分类列表
|
||||||
|
const getCategoryList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSurveyCateList({ corpId: corpId });
|
||||||
|
if (res.success && res.data) {
|
||||||
|
const cates = res.data.list || [];
|
||||||
|
categoryList.value = [
|
||||||
|
{ _id: '', name: '全部' },
|
||||||
|
...cates
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取分类列表失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择分类
|
||||||
|
const selectCategory = (cate) => {
|
||||||
|
currentCateId.value = cate._id || '';
|
||||||
|
page.value = 1;
|
||||||
|
surveyList.value = [];
|
||||||
|
loadSurveyList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索处理
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (searchTimer) {
|
||||||
|
clearTimeout(searchTimer);
|
||||||
|
}
|
||||||
|
searchTimer = setTimeout(() => {
|
||||||
|
page.value = 1;
|
||||||
|
surveyList.value = [];
|
||||||
|
loadSurveyList();
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载问卷列表
|
||||||
|
const loadSurveyList = async () => {
|
||||||
|
if (loading.value) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
corpId: corpId,
|
||||||
|
page: page.value,
|
||||||
|
pageSize: pageSize,
|
||||||
|
name: searchName.value.trim(),
|
||||||
|
status: 'enable',
|
||||||
|
showCount: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果选择了分类,添加分类ID
|
||||||
|
if (currentCateId.value) {
|
||||||
|
params.cateIds = [currentCateId.value];
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await getSurveyList(params);
|
||||||
|
if (res.success && res.data) {
|
||||||
|
const { list = [], total: count = 0 } = res.data;
|
||||||
|
|
||||||
|
if (page.value === 1) {
|
||||||
|
surveyList.value = list;
|
||||||
|
} else {
|
||||||
|
surveyList.value = [...surveyList.value, ...list];
|
||||||
|
}
|
||||||
|
total.value = count;
|
||||||
|
emptyText.value = '暂无问卷信息';
|
||||||
|
} else {
|
||||||
|
emptyText.value = res.message || '加载失败';
|
||||||
|
uni.showToast({
|
||||||
|
title: res.message || '获取问卷列表失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载问卷列表失败:', error);
|
||||||
|
emptyText.value = '加载失败';
|
||||||
|
uni.showToast({
|
||||||
|
title: '加载失败,请重试',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载更多
|
||||||
|
const loadMore = () => {
|
||||||
|
if (loading.value || surveyList.value.length >= total.value) return;
|
||||||
|
page.value += 1;
|
||||||
|
loadSurveyList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 预览问卷
|
||||||
|
const previewSurvey = (survey) => {
|
||||||
|
// 可以跳转到问卷预览页面或显示问卷详情
|
||||||
|
uni.showToast({
|
||||||
|
title: '预览功能开发中',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成随机字符串
|
||||||
|
const generateRandomString = (length) => {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 发送问卷
|
||||||
|
const sendSurvey = async (survey) => {
|
||||||
|
if (loading.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
// 获取医生信息
|
||||||
|
const doctorInfo = accountStore.doctorInfo;
|
||||||
|
userId.value = doctorInfo?.userid || accountStore.openid;
|
||||||
|
|
||||||
|
// 生成发送ID
|
||||||
|
const sendSurveyId = generateRandomString(10);
|
||||||
|
|
||||||
|
// 获取当前聊天的客户信息(从上一页传递)
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
const prevPage = pages[pages.length - 2];
|
||||||
|
|
||||||
|
// 这里需要从聊天页面获取客户信息
|
||||||
|
// 暂时使用事件传递方式
|
||||||
|
uni.$emit('sendSurvey', {
|
||||||
|
survey: survey,
|
||||||
|
corpId: corpId,
|
||||||
|
userId: userId.value,
|
||||||
|
sendSurveyId: sendSurveyId
|
||||||
|
});
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '已选择问卷',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 延迟返回
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('发送问卷失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '发送失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 返回
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getCategoryList();
|
||||||
|
loadSurveyList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.survey-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20rpx;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 16rpx 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-sidebar {
|
||||||
|
width: 200rpx;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-right: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-scroll {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-item {
|
||||||
|
padding: 32rpx 24rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-item.active {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #1890ff;
|
||||||
|
font-weight: bold;
|
||||||
|
border-left: 4rpx solid #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-list {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-scroll {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container,
|
||||||
|
.empty-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 100rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx 30rpx;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
line-height: 1.4;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-action {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn {
|
||||||
|
padding: 8rpx 32rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-more,
|
||||||
|
.no-more {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 30rpx 0;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
gap: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20rpx;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
70
utils/api.js
70
utils/api.js
@ -24,7 +24,26 @@ const urlsConfig = {
|
|||||||
saveCommonPhrase: 'saveCommonPhrase',
|
saveCommonPhrase: 'saveCommonPhrase',
|
||||||
deleteCommonPhrase: 'deleteCommonPhrase',
|
deleteCommonPhrase: 'deleteCommonPhrase',
|
||||||
getCommonPhraseCategories: 'getCommonPhraseCategories',
|
getCommonPhraseCategories: 'getCommonPhraseCategories',
|
||||||
saveCommonPhraseCategory: 'saveCommonPhraseCategory'
|
saveCommonPhraseCategory: 'saveCommonPhraseCategory',
|
||||||
|
// 个人常用语接口
|
||||||
|
getPersonalPhrases: 'getPersonalPhrases',
|
||||||
|
savePersonalPhrase: 'savePersonalPhrase',
|
||||||
|
deletePersonalPhrase: 'deletePersonalPhrase',
|
||||||
|
getPersonalPhraseCategories: 'getPersonalPhraseCategories',
|
||||||
|
savePersonalPhraseCategory: 'savePersonalPhraseCategory',
|
||||||
|
deletePersonalPhraseCategory: 'deletePersonalPhraseCategory',
|
||||||
|
// 宣教文章接口
|
||||||
|
getArticleCateList: 'getArticleCateList',
|
||||||
|
getArticleList: 'getArticleList',
|
||||||
|
getArticle: 'getArticle',
|
||||||
|
addArticleSendRecord: 'addArticleSendRecord'
|
||||||
|
},
|
||||||
|
|
||||||
|
survery: {
|
||||||
|
getSurveyCateList: 'getSurveryCateList',
|
||||||
|
getSurveyList: 'getList',
|
||||||
|
createSurveyRecord: 'createRecord',
|
||||||
|
getSurveyDetail: 'getDetail'
|
||||||
},
|
},
|
||||||
member: {
|
member: {
|
||||||
addCustomer: 'add',
|
addCustomer: 'add',
|
||||||
@ -41,7 +60,8 @@ const urlsConfig = {
|
|||||||
im: {
|
im: {
|
||||||
getUserSig: 'getUserSig',
|
getUserSig: 'getUserSig',
|
||||||
sendSystemMessage: "sendSystemMessage",
|
sendSystemMessage: "sendSystemMessage",
|
||||||
getChatRecordsByGroupId: "getChatRecordsByGroupId"
|
getChatRecordsByGroupId: "getChatRecordsByGroupId",
|
||||||
|
sendConsultRejectedMessage: "sendConsultRejectedMessage"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -75,3 +95,49 @@ export default async function api(urlId, data) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 宣教文章相关 API
|
||||||
|
export async function getArticleCateList(data) {
|
||||||
|
return api('getArticleCateList', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getArticleList(data) {
|
||||||
|
return api('getArticleList', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getArticle(data) {
|
||||||
|
return api('getArticle', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addArticleSendRecord(data) {
|
||||||
|
return api('addArticleSendRecord', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 问卷相关 API
|
||||||
|
export async function getSurveyCateList(data) {
|
||||||
|
return api('getSurveyCateList', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSurveyList(data) {
|
||||||
|
return api('getSurveyList', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createSurveyRecord(data) {
|
||||||
|
return api('createSurveyRecord', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSurveyDetail(data) {
|
||||||
|
return api('getSurveyDetail', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// IM 系统消息相关 API
|
||||||
|
export async function sendConsultRejectedMessage(data) {
|
||||||
|
return request({
|
||||||
|
url: '/getYoucanData/im',
|
||||||
|
data: {
|
||||||
|
type: 'sendConsultRejectedMessage',
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -363,7 +363,6 @@ class TimChatManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息并登录
|
|
||||||
async getUserInfoAndLogin(userID) {
|
async getUserInfoAndLogin(userID) {
|
||||||
try {
|
try {
|
||||||
if (userID) {
|
if (userID) {
|
||||||
@ -376,7 +375,6 @@ class TimChatManager {
|
|||||||
}
|
}
|
||||||
this.currentUserID = userInfo.userID
|
this.currentUserID = userInfo.userID
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentUserSig = await this.getUserSig(this.currentUserID)
|
this.currentUserSig = await this.getUserSig(this.currentUserID)
|
||||||
await this.loginTIM()
|
await this.loginTIM()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -711,26 +709,37 @@ class TimChatManager {
|
|||||||
// 获取消息所属的会话ID
|
// 获取消息所属的会话ID
|
||||||
const messageConversationID = convertedMessage.conversationID
|
const messageConversationID = convertedMessage.conversationID
|
||||||
|
|
||||||
|
// 检查是否为系统消息
|
||||||
|
const isSystemMsg = this.isSystemMessage(convertedMessage)
|
||||||
|
|
||||||
console.log('收到新消息:', {
|
console.log('收到新消息:', {
|
||||||
messageID: convertedMessage.ID,
|
messageID: convertedMessage.ID,
|
||||||
messageConversationID: messageConversationID,
|
messageConversationID: messageConversationID,
|
||||||
currentConversationID: this.currentConversationID,
|
currentConversationID: this.currentConversationID,
|
||||||
messageType: convertedMessage.type,
|
messageType: convertedMessage.type,
|
||||||
from: convertedMessage.from
|
from: convertedMessage.from,
|
||||||
|
isSystemMessage: isSystemMsg
|
||||||
})
|
})
|
||||||
// 判断是否为当前会话的消息(必须有currentConversationID且匹配才显示)
|
|
||||||
const isCurrentConversation = this.currentConversationID &&
|
// 判断是否为当前会话的消息
|
||||||
messageConversationID === this.currentConversationID
|
// 系统消息:只要会话ID匹配就显示(不要求必须有currentConversationID)
|
||||||
|
// 普通消息:必须有currentConversationID且匹配才显示
|
||||||
|
const isCurrentConversation = isSystemMsg
|
||||||
|
? messageConversationID === this.currentConversationID
|
||||||
|
: (this.currentConversationID && messageConversationID === this.currentConversationID)
|
||||||
|
|
||||||
console.log('消息会话匹配检查:', {
|
console.log('消息会话匹配检查:', {
|
||||||
isCurrentConversation,
|
isCurrentConversation,
|
||||||
|
isSystemMessage: isSystemMsg,
|
||||||
hasCurrentConversationID: !!this.currentConversationID,
|
hasCurrentConversationID: !!this.currentConversationID,
|
||||||
conversationIDMatch: messageConversationID === this.currentConversationID
|
conversationIDMatch: messageConversationID === this.currentConversationID
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isCurrentConversation) {
|
if (isCurrentConversation) {
|
||||||
// 当前会话的消息,触发回调
|
// 当前会话的消息,触发回调
|
||||||
console.log('✓ 消息属于当前会话,触发显示')
|
console.log('✓ 消息属于当前会话,触发显示')
|
||||||
this.triggerCallback('onMessageReceived', convertedMessage)
|
this.triggerCallback('onMessageReceived', convertedMessage)
|
||||||
// 处理已读状态
|
// 处理已读状态(系统消息也标记为已读)
|
||||||
if (this.currentConversationID) {
|
if (this.currentConversationID) {
|
||||||
this.markConversationAsRead(this.currentConversationID)
|
this.markConversationAsRead(this.currentConversationID)
|
||||||
}
|
}
|
||||||
@ -1093,7 +1102,52 @@ class TimChatManager {
|
|||||||
} else if (lastMessage.type === 'TIMSoundElem') {
|
} else if (lastMessage.type === 'TIMSoundElem') {
|
||||||
lastMessageText = '[语音]'
|
lastMessageText = '[语音]'
|
||||||
} else if (lastMessage.type === 'TIMCustomElem') {
|
} else if (lastMessage.type === 'TIMCustomElem') {
|
||||||
lastMessageText = lastMessage.payload.data || '[自定义消息]'
|
// 解析自定义消息
|
||||||
|
try {
|
||||||
|
const customData = JSON.parse(lastMessage.payload.data)
|
||||||
|
const messageType = customData.messageType
|
||||||
|
// 根据消息类型返回不同的预览文本
|
||||||
|
switch (messageType) {
|
||||||
|
case 'system_message':
|
||||||
|
lastMessageText = '[系统消息]'
|
||||||
|
break
|
||||||
|
case 'symptom':
|
||||||
|
lastMessageText = '[病情描述]'
|
||||||
|
break
|
||||||
|
case 'prescription':
|
||||||
|
lastMessageText = '[处方单]'
|
||||||
|
break
|
||||||
|
case 'refill':
|
||||||
|
lastMessageText = '[续方申请]'
|
||||||
|
break
|
||||||
|
case 'survey':
|
||||||
|
lastMessageText = '[问卷调查]'
|
||||||
|
break
|
||||||
|
case 'article':
|
||||||
|
lastMessageText = '[文章]'
|
||||||
|
break
|
||||||
|
case "consult_pending":
|
||||||
|
lastMessageText = '患者向团队发起咨询,请在1小时内接诊,超时将自动关闭会话'
|
||||||
|
break
|
||||||
|
case "consult_rejected":
|
||||||
|
lastMessageText = '患者向团队发起咨询,由于有紧急事务要处理暂时无法接受咨询.本次会话丿关闭'
|
||||||
|
break
|
||||||
|
case "consult_timeout":
|
||||||
|
lastMessageText = '患者向团队发起咨询,团队成员均未接受咨询,本次会话已自动关闭'
|
||||||
|
break
|
||||||
|
case "consult_accepted":
|
||||||
|
lastMessageText = '已接诊,会话已开始'
|
||||||
|
break
|
||||||
|
case "consult_ended":
|
||||||
|
lastMessageText = '已结束当前会话'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
lastMessageText = '[自定义消息]'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析自定义消息失败:', error)
|
||||||
|
lastMessageText = '[自定义消息]'
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
lastMessageText = '[未知消息类型]'
|
lastMessageText = '[未知消息类型]'
|
||||||
}
|
}
|
||||||
@ -2362,6 +2416,41 @@ class TimChatManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 工具方法
|
// 工具方法
|
||||||
|
// 判断是否为系统消息
|
||||||
|
isSystemMessage(message) {
|
||||||
|
if (message.type !== 'TIMCustomElem') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 payload.data 是否包含系统消息标记
|
||||||
|
try {
|
||||||
|
if (message.payload && message.payload.data) {
|
||||||
|
const data = typeof message.payload.data === 'string'
|
||||||
|
? JSON.parse(message.payload.data)
|
||||||
|
: message.payload.data
|
||||||
|
|
||||||
|
// 检查是否为系统消息类型
|
||||||
|
if (data.type === 'system_message') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 description 是否为系统消息标记
|
||||||
|
if (message.payload && message.payload.description === '系统消息标记') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧的系统消息格式
|
||||||
|
if (message.payload && message.payload.description === 'SYSTEM_NOTIFICATION') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('判断系统消息失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
filterMessage(message) {
|
filterMessage(message) {
|
||||||
if (message.type === 'TIMCustomElem' && message.payload && message.payload.data) {
|
if (message.type === 'TIMCustomElem' && message.payload && message.payload.data) {
|
||||||
if (message.payload.data === 'group_create' || message.payload.data === 'purchased') {
|
if (message.payload.data === 'group_create' || message.payload.data === 'purchased') {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user