Merge branch 'dev-wdb' of http://175.27.226.205:3000/huxuejian/ykt-wxapp into dev-wdb
This commit is contained in:
commit
18be6a46ad
10
pages.json
10
pages.json
@ -21,7 +21,7 @@
|
||||
{
|
||||
"path": "pages/home/case-home",
|
||||
"style": {
|
||||
"navigationBarTitleText": "病例"
|
||||
"navigationBarTitleText": "病历"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -190,12 +190,6 @@
|
||||
"navigationBarTitleText": "共享客户"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "patient-invite",
|
||||
"style": {
|
||||
"navigationBarTitleText": "邀请患者"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "patient-create",
|
||||
"style": {
|
||||
@ -311,7 +305,7 @@
|
||||
"pagePath": "pages/home/case-home",
|
||||
"iconPath": "static/tabbar/cart.png",
|
||||
"selectedIconPath": "static/tabbar/cart_selected.png",
|
||||
"text": "病例"
|
||||
"text": "病历"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/home/work-home",
|
||||
|
||||
1115
pages/case/case.vue
1115
pages/case/case.vue
File diff suppressed because it is too large
Load Diff
@ -64,7 +64,7 @@
|
||||
<view class="content">{{ i.taskContent || "暂无内容" }}</view>
|
||||
<view v-if="i.sendContent || (i.fileList && i.fileList.length > 0)" class="send-content-wrapper">
|
||||
<view class="send-content-section">
|
||||
<view class="send-content-label">【发送内容】</view>
|
||||
<view class="send-content-label">发送内容:</view>
|
||||
<view class="send-content-body">
|
||||
<view v-if="i.sendContent" class="send-text">{{ i.sendContent }}</view>
|
||||
<view v-if="i.fileList && i.fileList.length > 0" class="file-list">
|
||||
@ -204,6 +204,7 @@ import dayjs from "dayjs";
|
||||
import api from "@/utils/api";
|
||||
import useAccountStore from "@/store/account";
|
||||
import { toast } from "@/utils/widget";
|
||||
import { handleFollowUpMessages } from "@/utils/send-message-helper";
|
||||
import {
|
||||
getTodoEventTypeLabel,
|
||||
getTodoEventTypeOptions,
|
||||
@ -221,6 +222,15 @@ const accountStore = useAccountStore();
|
||||
const { account, doctorInfo } = storeToRefs(accountStore);
|
||||
const { getDoctorInfo } = accountStore;
|
||||
|
||||
function getUserId() {
|
||||
return doctorInfo.value?.userid || "";
|
||||
}
|
||||
|
||||
function getCorpId() {
|
||||
const team = uni.getStorageSync("ykt_case_current_team") || {};
|
||||
return team.corpId || doctorInfo.value?.corpId || "";
|
||||
}
|
||||
|
||||
const statusTabs = [
|
||||
{ label: "全部", value: "all" },
|
||||
{ label: "待处理", value: "processing" },
|
||||
@ -275,23 +285,6 @@ const typeSelectedMap = computed(() => {
|
||||
}, {});
|
||||
});
|
||||
|
||||
function getUserId() {
|
||||
const d = doctorInfo.value || {};
|
||||
const a = account.value || {};
|
||||
return (
|
||||
String(
|
||||
d.userid || d.userId || d.corpUserId || a.userid || a.userId || ""
|
||||
) || ""
|
||||
);
|
||||
}
|
||||
|
||||
function getCorpId() {
|
||||
const t = uni.getStorageSync("ykt_case_current_team") || {};
|
||||
const a = account.value || {};
|
||||
const d = doctorInfo.value || {};
|
||||
return String(t.corpId || a.corpId || d.corpId || "") || "";
|
||||
}
|
||||
|
||||
async function ensureDoctor() {
|
||||
if (doctorInfo.value) return;
|
||||
if (!account.value?.openid) return;
|
||||
@ -537,9 +530,12 @@ async function sendFollowUp(todo) {
|
||||
messages.push({
|
||||
type: "article",
|
||||
content: {
|
||||
_id: file.file?._id || file._id,
|
||||
title: file.file?.name || file.name || "宣教文章",
|
||||
url: file.file?.url || file.URL,
|
||||
desc: file.file?.subtitle || "",
|
||||
subtitle: file.file?.subtitle || "",
|
||||
cover: file.file?.cover || "",
|
||||
articleId: file.file?._id || file._id,
|
||||
},
|
||||
});
|
||||
} else if (file.type === "questionnaire" && file.file?.surveryId) {
|
||||
@ -547,7 +543,8 @@ async function sendFollowUp(todo) {
|
||||
messages.push({
|
||||
type: "questionnaire",
|
||||
content: {
|
||||
title: file.file?.name || file.name || "问卷",
|
||||
_id: file.file?._id || file._id,
|
||||
name: file.file?.name || file.name || "问卷",
|
||||
surveryId: file.file?.surveryId || file.surveryId,
|
||||
url: file.file?.url || file.URL,
|
||||
},
|
||||
@ -556,15 +553,19 @@ async function sendFollowUp(todo) {
|
||||
}
|
||||
}
|
||||
|
||||
// 触发事件,通知父组件发送消息
|
||||
uni.$emit("send-followup-message", {
|
||||
messages,
|
||||
followupId: todo._id,
|
||||
followupData: todo,
|
||||
// 调用统一的消息发送处理函数
|
||||
const success = await handleFollowUpMessages(messages, {
|
||||
userId: getUserId(),
|
||||
customerId: props.archiveId,
|
||||
customerName: props.data?.name || "",
|
||||
corpId: getCorpId(),
|
||||
env: __VITE_ENV__,
|
||||
});
|
||||
|
||||
toast("消息已发送");
|
||||
uni.navigateBack();
|
||||
if (success) {
|
||||
toast("消息已发送");
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
|
||||
// ---- filter popup ----
|
||||
@ -863,6 +864,7 @@ watch(
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
width: 330rpx;
|
||||
}
|
||||
.file-type-image .file-name {
|
||||
color: #0877f1;
|
||||
|
||||
@ -1,35 +1,67 @@
|
||||
<template>
|
||||
<view class="manage-container">
|
||||
<view class="group-list">
|
||||
<view v-for="(item, index) in groups" :key="item._id" class="group-item">
|
||||
<view class="left-action" :class="{ disabled: Boolean(item.parentGroupId) || isSort }" @click="handleDelete(item, index)">
|
||||
<uni-icons type="minus-filled" size="24" :color="Boolean(item.parentGroupId) || isSort ? '#ddd' : '#ff4d4f'"></uni-icons>
|
||||
</view>
|
||||
<view class="group-name">
|
||||
<view class="name-row">
|
||||
<text class="name-text">{{ item.groupName }}</text>
|
||||
<text v-if="item.parentGroupId" class="corp-tag">机构</text>
|
||||
</view>
|
||||
<text v-if="item.description" class="desc">{{ item.description }}</text>
|
||||
</view>
|
||||
<view class="right-actions">
|
||||
<uni-icons type="compose" size="24" :color="isSort ? '#ddd' : '#5d8aff'" class="icon-edit" @click="handleEdit(item, index)"></uni-icons>
|
||||
<template v-if="isSort">
|
||||
<uni-icons type="arrowup" size="20" color="#5d8aff" class="icon-sort" @click="moveUp(index)"></uni-icons>
|
||||
<uni-icons type="arrowdown" size="20" color="#5d8aff" class="icon-sort" @click="moveDown(index)"></uni-icons>
|
||||
</template>
|
||||
<uni-icons v-else type="bars" size="24" color="#5d8aff" class="icon-drag" @click="enterSort"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view scroll-y class="sort-scroll">
|
||||
<movable-area class="drag-area" :style="{ height: dragAreaHeight + 'px' }">
|
||||
<movable-view
|
||||
v-for="(item, index) in groups"
|
||||
:key="item._id"
|
||||
class="drag-item"
|
||||
direction="vertical"
|
||||
:disabled="dragEnabledId !== String(item._id)"
|
||||
:y="Number(item._y || 0)"
|
||||
:animation="draggingId !== String(item._id)"
|
||||
@change="(e) => onDragChange(e, item)"
|
||||
@touchend="() => onTouchEnd(item)"
|
||||
@touchcancel="() => onTouchEnd(item)"
|
||||
>
|
||||
<view class="group-item" :class="{ 'is-dragging': draggingId === String(item._id) }">
|
||||
<view
|
||||
class="left-action"
|
||||
:class="{ disabled: Boolean(item.parentGroupId) || Boolean(dragEnabledId) }"
|
||||
@click.stop="handleDelete(item, index)"
|
||||
@longpress.stop
|
||||
>
|
||||
<uni-icons
|
||||
type="minus-filled"
|
||||
size="24"
|
||||
:color="Boolean(item.parentGroupId) || Boolean(dragEnabledId) ? '#ddd' : '#ff4d4f'"
|
||||
></uni-icons>
|
||||
</view>
|
||||
<view class="group-name">
|
||||
<view class="name-row">
|
||||
<text class="name-text">{{ item.groupName }}</text>
|
||||
<text v-if="item.parentGroupId" class="corp-tag">机构</text>
|
||||
</view>
|
||||
<text v-if="item.description" class="desc">{{ item.description }}</text>
|
||||
</view>
|
||||
<view class="right-actions">
|
||||
<uni-icons
|
||||
type="compose"
|
||||
size="24"
|
||||
:color="Boolean(dragEnabledId) ? '#ddd' : '#5d8aff'"
|
||||
class="icon-edit"
|
||||
@click.stop="handleEdit(item, index)"
|
||||
@longpress.stop
|
||||
></uni-icons>
|
||||
<view
|
||||
class="drag-handle"
|
||||
@touchstart="() => startHoldToDrag(item)"
|
||||
@touchend="() => cancelHoldToDrag()"
|
||||
@touchcancel="() => cancelHoldToDrag()"
|
||||
>
|
||||
<uni-icons type="bars" size="24" color="#5d8aff" class="icon-drag"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</movable-view>
|
||||
</movable-area>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- Bottom Button -->
|
||||
<view class="footer">
|
||||
<template v-if="isSort">
|
||||
<button class="add-btn plain" @click="cancelSort">取消</button>
|
||||
<button class="add-btn" @click="saveSort">保存</button>
|
||||
</template>
|
||||
<button v-else class="add-btn" @click="handleAdd">添加新分组</button>
|
||||
<button class="add-btn" :disabled="Boolean(dragEnabledId)" @click="handleAdd">添加新分组</button>
|
||||
</view>
|
||||
|
||||
<!-- Dialog -->
|
||||
@ -49,7 +81,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import api from '@/utils/api';
|
||||
@ -59,7 +91,15 @@ import { hideLoading, loading, toast } from '@/utils/widget';
|
||||
// State
|
||||
const groups = ref([]);
|
||||
const originalGroups = ref([]);
|
||||
const isSort = ref(false);
|
||||
|
||||
const ITEM_HEIGHT = 74; // px,需与样式保持一致
|
||||
const draggingId = ref('');
|
||||
const dragEnabledId = ref('');
|
||||
const savingSort = ref(false);
|
||||
const lastSavedOrderKey = ref('');
|
||||
const holdTimer = ref(null);
|
||||
const holdCandidateId = ref('');
|
||||
const dragAreaHeight = computed(() => (groups.value.length || 0) * ITEM_HEIGHT);
|
||||
|
||||
const GROUPS_RELOAD_KEY = 'ykt_case_groups_need_reload';
|
||||
const CURRENT_TEAM_STORAGE_KEY = 'ykt_case_current_team';
|
||||
@ -126,8 +166,9 @@ async function loadGroups() {
|
||||
}
|
||||
const list = Array.isArray(res.data) ? res.data : Array.isArray(res.data?.data) ? res.data.data : [];
|
||||
const sorted = sortGroupList(list);
|
||||
groups.value = sorted.map((i) => ({ ...i }));
|
||||
originalGroups.value = sorted.map((i) => ({ ...i }));
|
||||
groups.value = sorted.map((i, idx) => ({ ...i, _y: idx * ITEM_HEIGHT }));
|
||||
originalGroups.value = sorted.map((i, idx) => ({ ...i, _y: idx * ITEM_HEIGHT }));
|
||||
lastSavedOrderKey.value = getOrderKey(groups.value);
|
||||
} catch (e) {
|
||||
toast('获取分组失败');
|
||||
} finally {
|
||||
@ -144,7 +185,7 @@ const dialogTitle = ref('添加新分组');
|
||||
|
||||
// Methods
|
||||
const handleAdd = () => {
|
||||
if (isSort.value) return;
|
||||
if (dragEnabledId.value) return;
|
||||
dialogMode.value = 'add';
|
||||
dialogTitle.value = '添加新分组';
|
||||
inputValue.value = '';
|
||||
@ -152,7 +193,7 @@ const handleAdd = () => {
|
||||
};
|
||||
|
||||
const handleEdit = (item, index) => {
|
||||
if (isSort.value) return;
|
||||
if (dragEnabledId.value) return;
|
||||
dialogMode.value = 'edit';
|
||||
dialogTitle.value = '编辑分组名称';
|
||||
inputValue.value = item.groupName || '';
|
||||
@ -161,7 +202,7 @@ const handleEdit = (item, index) => {
|
||||
};
|
||||
|
||||
const handleDelete = (item, index) => {
|
||||
if (isSort.value) return;
|
||||
if (dragEnabledId.value) return;
|
||||
if (item?.parentGroupId) return;
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
@ -243,56 +284,114 @@ const handleSave = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
function enterSort() {
|
||||
if (!groups.value.length) return;
|
||||
isSort.value = true;
|
||||
function initDragPositions() {
|
||||
groups.value = groups.value.map((i, idx) => ({ ...i, _y: idx * ITEM_HEIGHT }));
|
||||
}
|
||||
|
||||
function cancelSort() {
|
||||
isSort.value = false;
|
||||
groups.value = originalGroups.value.map((i) => ({ ...i }));
|
||||
function getOrderKey(list) {
|
||||
return (Array.isArray(list) ? list : []).map((i) => String(i?._id || '')).join('|');
|
||||
}
|
||||
|
||||
function moveUp(index) {
|
||||
if (!isSort.value) return;
|
||||
if (index <= 0) return;
|
||||
const next = groups.value.slice();
|
||||
const tmp = next[index - 1];
|
||||
next[index - 1] = next[index];
|
||||
next[index] = tmp;
|
||||
groups.value = next;
|
||||
function clamp(n, min, max) {
|
||||
return Math.max(min, Math.min(max, n));
|
||||
}
|
||||
|
||||
function moveDown(index) {
|
||||
if (!isSort.value) return;
|
||||
if (index >= groups.value.length - 1) return;
|
||||
const next = groups.value.slice();
|
||||
const tmp = next[index + 1];
|
||||
next[index + 1] = next[index];
|
||||
next[index] = tmp;
|
||||
groups.value = next;
|
||||
function findIndexById(id) {
|
||||
const sid = String(id || '');
|
||||
return groups.value.findIndex((g) => String(g?._id || '') === sid);
|
||||
}
|
||||
|
||||
async function saveSort() {
|
||||
const teamId = getTeamId();
|
||||
if (!teamId) return;
|
||||
loading('');
|
||||
function setYByIndex(activeId, activeY) {
|
||||
const sid = String(activeId || '');
|
||||
groups.value.forEach((g, idx) => {
|
||||
if (sid && String(g?._id || '') === sid && typeof activeY === 'number') g._y = activeY;
|
||||
else g._y = idx * ITEM_HEIGHT;
|
||||
});
|
||||
}
|
||||
|
||||
function clearHoldTimer() {
|
||||
if (holdTimer.value) {
|
||||
clearTimeout(holdTimer.value);
|
||||
holdTimer.value = null;
|
||||
}
|
||||
holdCandidateId.value = '';
|
||||
}
|
||||
|
||||
async function onTouchEnd(item) {
|
||||
const id = String(item?._id || '');
|
||||
clearHoldTimer();
|
||||
if (!id) return;
|
||||
|
||||
// 只有当本次已进入拖动时,才执行拖动结束逻辑
|
||||
if (dragEnabledId.value === id) {
|
||||
draggingId.value = '';
|
||||
setYByIndex('', null);
|
||||
dragEnabledId.value = '';
|
||||
await maybeAutoSaveSort();
|
||||
}
|
||||
}
|
||||
|
||||
function startHoldToDrag(item) {
|
||||
const id = String(item?._id || '');
|
||||
if (!id) return;
|
||||
if (savingSort.value) return;
|
||||
if (dragEnabledId.value) return;
|
||||
clearHoldTimer();
|
||||
holdCandidateId.value = id;
|
||||
holdTimer.value = setTimeout(() => {
|
||||
if (holdCandidateId.value !== id) return;
|
||||
initDragPositions();
|
||||
dragEnabledId.value = id;
|
||||
draggingId.value = id;
|
||||
}, 350);
|
||||
}
|
||||
|
||||
function cancelHoldToDrag() {
|
||||
clearHoldTimer();
|
||||
}
|
||||
|
||||
async function maybeAutoSaveSort() {
|
||||
if (savingSort.value) return;
|
||||
const key = getOrderKey(groups.value);
|
||||
if (!key || key === lastSavedOrderKey.value) return;
|
||||
|
||||
savingSort.value = true;
|
||||
try {
|
||||
const teamId = getTeamId();
|
||||
if (!teamId) return;
|
||||
const data = groups.value.map((i, idx) => ({ _id: i._id, sortOrder: idx }));
|
||||
const res = await api('sortGroups', { teamId, data });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '保存失败');
|
||||
return;
|
||||
}
|
||||
toast('保存成功');
|
||||
lastSavedOrderKey.value = key;
|
||||
originalGroups.value = groups.value.map((i, idx) => ({ ...i, _y: idx * ITEM_HEIGHT }));
|
||||
uni.setStorageSync(GROUPS_RELOAD_KEY, 1);
|
||||
isSort.value = false;
|
||||
await loadGroups();
|
||||
toast('已保存');
|
||||
} finally {
|
||||
hideLoading();
|
||||
savingSort.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onDragChange(e, item) {
|
||||
const detail = e?.detail || {};
|
||||
if (detail.source && detail.source !== 'touch') return;
|
||||
const id = String(item?._id || '');
|
||||
if (!id) return;
|
||||
if (dragEnabledId.value !== id) return;
|
||||
const from = findIndexById(id);
|
||||
if (from < 0) return;
|
||||
const y = Number(detail.y || 0);
|
||||
const len = groups.value.length;
|
||||
const to = clamp(Math.round(y / ITEM_HEIGHT), 0, Math.max(0, len - 1));
|
||||
if (to !== from) {
|
||||
const moved = groups.value.splice(from, 1)[0];
|
||||
groups.value.splice(to, 0, moved);
|
||||
}
|
||||
setYByIndex(id, y);
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
loadGroups();
|
||||
});
|
||||
@ -309,12 +408,29 @@ onLoad(() => {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.sort-scroll {
|
||||
max-height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
.drag-area {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.drag-item {
|
||||
width: 100%;
|
||||
height: 74px;
|
||||
}
|
||||
|
||||
.group-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
&.is-dragging {
|
||||
background: #f7fbff;
|
||||
}
|
||||
|
||||
.left-action {
|
||||
margin-right: 15px;
|
||||
&.disabled {
|
||||
@ -368,20 +484,29 @@ onLoad(() => {
|
||||
background-color: #fff;
|
||||
padding: 15px 20px 30px; // Safe area padding
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.add-btn {
|
||||
background-color: #0877F1;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
border: none;
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.plain {
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
@ -390,6 +515,10 @@ onLoad(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
// Custom Dialog Styles
|
||||
.dialog-mask {
|
||||
position: fixed;
|
||||
|
||||
@ -46,6 +46,33 @@
|
||||
<view class="counter">{{ (form.taskContent || '').length }}/200</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="block">
|
||||
<view class="block-title">向患者发送</view>
|
||||
<view class="textarea-box">
|
||||
<textarea v-model="form.sendContent" class="textarea" placeholder="请输入要发送给患者的内容" maxlength="200" />
|
||||
<view class="counter">{{ (form.sendContent || '').length }}/200</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="row clickable" @click="chooseFile">
|
||||
<view class="left">
|
||||
<view class="label">添加附件</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<uni-icons type="plusempty" size="16" color="#0877F1" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="showFileList.length" class="file-list">
|
||||
<view v-for="(i, index) in showFileList" :key="String(i._k || index)" class="file-item">
|
||||
<view class="file-main">
|
||||
<view v-if="i.typeStr" class="file-type">{{ i.typeStr }}</view>
|
||||
<view class="file-name">{{ i.fileName }}</view>
|
||||
</view>
|
||||
<uni-icons type="closeempty" size="18" color="#999" @click="removeFile(index)" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
@ -106,6 +133,7 @@ import api from '@/utils/api';
|
||||
import useAccountStore from '@/store/account';
|
||||
import { toast } from '@/utils/widget';
|
||||
import { getTodoEventTypeLabel, getTodoEventTypeOptions } from '@/utils/todo-const';
|
||||
import { chooseAndUploadImage } from '@/utils/file';
|
||||
|
||||
const archiveId = ref('');
|
||||
const archiveName = ref('');
|
||||
@ -127,6 +155,8 @@ const form = reactive({
|
||||
executorName: '', // anotherName
|
||||
eventType: '',
|
||||
taskContent: '',
|
||||
sendContent: '',
|
||||
fileList: [],
|
||||
});
|
||||
|
||||
const eventTypeLabel = computed(() => getTodoEventTypeLabel(form.eventType));
|
||||
@ -165,6 +195,8 @@ function resetForm(keepCustomer = false) {
|
||||
form.executorName = '';
|
||||
form.eventType = '';
|
||||
form.taskContent = '';
|
||||
form.sendContent = '';
|
||||
form.fileList = [];
|
||||
if (!keepCustomer) {
|
||||
archiveId.value = '';
|
||||
archiveName.value = '';
|
||||
@ -174,6 +206,35 @@ function resetForm(keepCustomer = false) {
|
||||
uni.setStorageSync('select-mamagement-plan', '');
|
||||
}
|
||||
|
||||
const QUESTIONNAIRE_ICON =
|
||||
'https://796f-youcan-clouddev-1-8ewcqf31dbb2b5-1317294507.tcb.qcloud.la/other/19-%E9%97%AE%E5%8D%B7.png?sign=55a4cd77c418b2c548b65792a2cf6bce&t=1701328694';
|
||||
const ARTICLE_ICON =
|
||||
'https://796f-youcan-clouddev-1-8ewcqf31dbb2b5-1317294507.tcb.qcloud.la/other/18-%E5%AE%A3%E6%95%99.png?sign=26f221d14fd57a2ff0a106dfb01a5e7a&t=1701328694';
|
||||
|
||||
const showFileList = computed(() =>
|
||||
(Array.isArray(form.fileList) ? form.fileList : []).map((i, idx) => {
|
||||
const type = i?.type;
|
||||
const fileType = i?.file && typeof i.file.type === 'string' ? i.file.type : '';
|
||||
let typeStr = '';
|
||||
if (type === 'image' || fileType.includes('image')) typeStr = '【图片】';
|
||||
else if (type === 'video' || fileType.includes('video')) typeStr = '【视频】';
|
||||
else if (fileType === 'article') typeStr = '【文章】';
|
||||
else if (fileType === 'questionnaire') typeStr = '【问卷】';
|
||||
else if (type === 'link') typeStr = '【链接】';
|
||||
|
||||
const fileName =
|
||||
String(i?.file?.name || i?.file?.title || i?.name || i?.URL || '').trim() ||
|
||||
`附件${idx + 1}`;
|
||||
|
||||
return {
|
||||
...i,
|
||||
_k: `${idx}_${String(i?.type || '')}_${String(i?.URL || '')}`,
|
||||
typeStr,
|
||||
fileName,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
async function ensureDoctor() {
|
||||
if (doctorInfo.value) return;
|
||||
if (!account.value?.openid) return;
|
||||
@ -272,6 +333,9 @@ async function save() {
|
||||
const customerName = String(customer.name || archiveName.value || '');
|
||||
const customerUserId = String(customer.externalUserId || customer.customerUserId || '') || '';
|
||||
|
||||
const enableSend = !!(String(form.sendContent || '').trim() || (Array.isArray(form.fileList) && form.fileList.length));
|
||||
const fileList = Array.isArray(form.fileList) ? form.fileList : [];
|
||||
|
||||
const params = {
|
||||
corpId,
|
||||
customerId,
|
||||
@ -283,15 +347,15 @@ async function save() {
|
||||
userId: form.executorUserId || userId,
|
||||
taskList: [
|
||||
{
|
||||
enableSend: false,
|
||||
enableSend,
|
||||
eventType: form.eventType,
|
||||
executeMethod: 'todo',
|
||||
executorUserId: form.executorUserId || userId,
|
||||
planExecutionTime: form.planExecutionTime,
|
||||
sendContent: '',
|
||||
sendContent: String(form.sendContent || ''),
|
||||
taskContent: form.taskContent,
|
||||
taskId: `${Date.now()}_${Math.random().toString(16).slice(2)}`,
|
||||
fileList: [],
|
||||
fileList,
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -324,6 +388,81 @@ function pickExecutor(m) {
|
||||
function closeExecutorPicker() {
|
||||
executorPopup.value?.close?.();
|
||||
}
|
||||
|
||||
function removeFile(index) {
|
||||
if (!Array.isArray(form.fileList)) form.fileList = [];
|
||||
form.fileList.splice(index, 1);
|
||||
}
|
||||
|
||||
function chooseFile() {
|
||||
uni.showActionSheet({
|
||||
itemList: ['图片', '文章', '问卷'],
|
||||
success: ({ tapIndex }) => {
|
||||
if (tapIndex === 0) chooseImage();
|
||||
else if (tapIndex === 1) chooseArticle();
|
||||
else if (tapIndex === 2) chooseQuestionnaire();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function chooseImage() {
|
||||
const url = await chooseAndUploadImage({ count: 1 });
|
||||
if (!url) return;
|
||||
form.fileList.push({
|
||||
type: 'image',
|
||||
URL: url,
|
||||
file: {
|
||||
type: 'image',
|
||||
name: `图片_${dayjs().format('MMDD_HHmmss')}.jpg`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function chooseArticle() {
|
||||
const eventName = `on-select-article_${Date.now()}`;
|
||||
uni.navigateTo({
|
||||
url: `/pages/message/article-list?select=1&eventName=${eventName}`,
|
||||
});
|
||||
uni.$once(eventName, (data) => {
|
||||
const corpId = getCorpId();
|
||||
const articleId = String(data?._id || data?.id || '');
|
||||
if (!articleId) return;
|
||||
const url = `${__VITE_ENV__?.MP_PATIENT_PAGE_BASE_URL || ''}pages/article/index?id=${articleId}&corpId=${corpId}`;
|
||||
form.fileList.push({
|
||||
type: 'link',
|
||||
URL: String(data?.cover || data?.imgUrl || '') || ARTICLE_ICON,
|
||||
file: {
|
||||
type: 'article',
|
||||
name: String(data?.title || '宣教文章'),
|
||||
subtitle: String(data?.summary || data?.desc || ''),
|
||||
url,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function chooseQuestionnaire() {
|
||||
const eventName = `on-select-survey_${Date.now()}`;
|
||||
uni.navigateTo({
|
||||
url: `/pages/message/survey-list?select=1&eventName=${eventName}&patientId=${archiveId.value}&customerName=${archiveName.value || ''}`,
|
||||
});
|
||||
uni.$once(eventName, (data) => {
|
||||
const corpId = getCorpId();
|
||||
const surveryId = String(data?._id || data?.surveryId || '');
|
||||
if (!surveryId) return;
|
||||
const url = `${__VITE_ENV__?.MP_PATIENT_PAGE_BASE_URL || ''}pages/survery/fill?corpId=${corpId}&surveryId=${surveryId}`;
|
||||
form.fileList.push({
|
||||
type: 'link',
|
||||
URL: QUESTIONNAIRE_ICON,
|
||||
file: {
|
||||
type: 'questionnaire',
|
||||
name: String(data?.name || '问卷'),
|
||||
surveryId,
|
||||
url,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -440,6 +579,39 @@ function closeExecutorPicker() {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
.file-list {
|
||||
padding: 0 14px 14px;
|
||||
}
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
padding: 12px 12px;
|
||||
background: #f5f6f8;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.file-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
gap: 8px;
|
||||
}
|
||||
.file-type {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
.file-name {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 240px;
|
||||
}
|
||||
.toggle-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -1,225 +0,0 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="content">
|
||||
<view class="code-row">
|
||||
<text class="code-text">{{ displayTeamCode }}</text>
|
||||
<view class="help" @click="showHelp">
|
||||
<uni-icons type="help-filled" size="18" color="#0877F1"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="qr-card">
|
||||
<image class="qr-image" :src="qrImageUrl" mode="aspectFit" @click="previewQr" />
|
||||
</view>
|
||||
|
||||
<view class="tips">
|
||||
<text class="tips-title">微信扫一扫上面的二维码</text>
|
||||
<text class="tips-sub">进入团队首页,即可发起线上咨询、建档授权等服务</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<button class="footer-btn outline" @click="saveQr">保存图片</button>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="footer-btn primary" open-type="share">分享到微信</button>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<button class="footer-btn primary" @click="shareFallback">分享到微信</button>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { onLoad, onShareAppMessage } from '@dcloudio/uni-app';
|
||||
|
||||
const teamCode = ref('133****3356');
|
||||
const qrImageUrl = ref('');
|
||||
|
||||
const displayTeamCode = computed(() => `${teamCode.value}服务团队码`);
|
||||
|
||||
const buildQrUrl = (text) => {
|
||||
// 仅用于演示UI:实际项目建议由后端返回可扫码的二维码图片地址
|
||||
const encoded = encodeURIComponent(text);
|
||||
return `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encoded}`;
|
||||
};
|
||||
|
||||
onLoad((query) => {
|
||||
if (query?.teamCode) teamCode.value = String(query.teamCode);
|
||||
if (query?.qrUrl) {
|
||||
qrImageUrl.value = String(query.qrUrl);
|
||||
} else {
|
||||
qrImageUrl.value = buildQrUrl(`TEAM_CODE:${teamCode.value}`);
|
||||
}
|
||||
});
|
||||
|
||||
const showHelp = () => {
|
||||
uni.showModal({
|
||||
title: '服务团队码',
|
||||
content: '患者微信扫一扫二维码进入团队首页,可发起线上咨询、建档授权等服务。',
|
||||
showCancel: false
|
||||
});
|
||||
};
|
||||
|
||||
const previewQr = () => {
|
||||
if (!qrImageUrl.value) return;
|
||||
uni.previewImage({ urls: [qrImageUrl.value] });
|
||||
};
|
||||
|
||||
const saveQr = () => {
|
||||
if (!qrImageUrl.value) {
|
||||
uni.showToast({ title: '二维码生成中,请稍后', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '保存中...' });
|
||||
uni.downloadFile({
|
||||
url: qrImageUrl.value,
|
||||
success: (res) => {
|
||||
const filePath = res.tempFilePath;
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath,
|
||||
success: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '已保存到相册', icon: 'success' });
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showModal({
|
||||
title: '保存失败',
|
||||
content: '请在系统设置中允许保存到相册后重试。',
|
||||
confirmText: '去设置',
|
||||
cancelText: '取消',
|
||||
success: (r) => {
|
||||
if (r.confirm) {
|
||||
uni.openSetting?.({});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '下载二维码失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const shareFallback = () => {
|
||||
uni.showToast({ title: '请使用右上角菜单分享', icon: 'none' });
|
||||
};
|
||||
|
||||
onShareAppMessage(() => {
|
||||
return {
|
||||
title: '邀请您加入服务团队',
|
||||
path: `/pages/case/patient-invite?teamCode=${encodeURIComponent(teamCode.value)}`
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #ffffff;
|
||||
padding-bottom: calc(76px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 22px 16px 0;
|
||||
}
|
||||
|
||||
.code-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.code-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.help {
|
||||
margin-left: 8px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qr-card {
|
||||
margin: 18px auto 0;
|
||||
width: 280px;
|
||||
height: 280px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qr-image {
|
||||
width: 260px;
|
||||
height: 260px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.tips-title {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #3a3a3a;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.tips-sub {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.footer-btn {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.footer-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.footer-btn.outline {
|
||||
background: #fff;
|
||||
color: #0877F1;
|
||||
border: 1px solid #0877F1;
|
||||
}
|
||||
|
||||
.footer-btn.primary {
|
||||
background: #0877F1;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@ -75,7 +75,7 @@
|
||||
<text class="patient-meta">{{ patient.gender }}/{{ patient.age }}岁</text>
|
||||
</view>
|
||||
<view class="patient-tags">
|
||||
<view v-for="(tag, tIndex) in patient.tags" :key="tIndex" class="tag">
|
||||
<view v-for="(tag, tIndex) in resolveGroupTags(patient)" :key="tIndex" class="tag">
|
||||
{{ tag }}
|
||||
</view>
|
||||
</view>
|
||||
@ -163,6 +163,16 @@ const tabs = computed(() => {
|
||||
const isBatchMode = ref(false);
|
||||
const selectedItems = ref([]); // Stores patient phone or unique ID
|
||||
|
||||
const groupNameMap = computed(() => {
|
||||
const map = new Map();
|
||||
(Array.isArray(teamGroups.value) ? teamGroups.value : []).forEach((g) => {
|
||||
const id = g && g._id ? String(g._id) : '';
|
||||
const name = g && g.groupName ? String(g.groupName) : '';
|
||||
if (id && name) map.set(id, name);
|
||||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
// Team Members Map
|
||||
const userNameMap = ref({});
|
||||
|
||||
@ -329,6 +339,20 @@ function getSelectId(patient) {
|
||||
return patient?._id || patient?.id || patient?.phone || patient?.mobile || '';
|
||||
}
|
||||
|
||||
function getPatientGroupIds(patient) {
|
||||
const raw = patient?.groupIds ?? patient?.groupIdList ?? patient?.groupId ?? patient?.groups;
|
||||
if (Array.isArray(raw)) return raw.map(String).filter(Boolean);
|
||||
if (typeof raw === 'string' || typeof raw === 'number') return [String(raw)].filter(Boolean);
|
||||
return [];
|
||||
}
|
||||
|
||||
function resolveGroupTags(patient) {
|
||||
const ids = getPatientGroupIds(patient);
|
||||
if (!ids.length) return [];
|
||||
const map = groupNameMap.value;
|
||||
return ids.map((id) => map.get(String(id))).filter(Boolean);
|
||||
}
|
||||
|
||||
function parseCreateTime(value) {
|
||||
if (!value) return null;
|
||||
if (typeof value === 'number') return dayjs(value);
|
||||
@ -354,6 +378,7 @@ function formatPatient(raw) {
|
||||
|
||||
const createTime = parseCreateTime(raw?.createTime);
|
||||
const createTimeStr = createTime ? createTime.format('YYYY-MM-DD HH:mm') : '';
|
||||
const createTimeTs = createTime ? createTime.valueOf() : 0;
|
||||
|
||||
// 优先使用后端返回的 tagNames(标签名称数组)
|
||||
const rawTagNames = asArray(raw?.tagNames).filter((i) => typeof i === 'string' && i.trim());
|
||||
@ -392,6 +417,7 @@ function formatPatient(raw) {
|
||||
mobiles,
|
||||
mobile,
|
||||
createTime: createTimeStr,
|
||||
createTimeTs,
|
||||
creator: raw?.creatorName || raw?.creator || '',
|
||||
hospitalId: raw?.customerNumber || raw?.hospitalId || '',
|
||||
record,
|
||||
@ -548,16 +574,11 @@ const patientList = computed(() => {
|
||||
|
||||
// New Patient Filter (Last 7 days)
|
||||
if (currentTab.value.kind === 'new') {
|
||||
const now = dayjs();
|
||||
const sevenDaysAgo = now.subtract(7, 'day').valueOf();
|
||||
const sevenDaysAgo = dayjs().subtract(7, 'day').startOf('day').valueOf();
|
||||
const flatList = all
|
||||
.map((p) => {
|
||||
const t = parseCreateTime(p.createTime)?.valueOf();
|
||||
return t ? { ...p, _ts: t } : null;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.filter((p) => p._ts >= sevenDaysAgo)
|
||||
.sort((a, b) => b._ts - a._ts);
|
||||
.filter((p) => Number(p?.createTimeTs || 0) >= sevenDaysAgo)
|
||||
.slice()
|
||||
.sort((a, b) => Number(b?.createTimeTs || 0) - Number(a?.createTimeTs || 0));
|
||||
return [{ letter: '最近新增', data: flatList }];
|
||||
}
|
||||
|
||||
@ -717,11 +738,11 @@ const openVerifyEntry = () => {
|
||||
};
|
||||
|
||||
const openAddCustomerServiceEntry = () => {
|
||||
uni.showToast({ title: '添加客服功能待接入', icon: 'none' });
|
||||
uni.navigateTo({ url: '/pages/work/service/contact-service' });
|
||||
};
|
||||
|
||||
const openInvitePatientEntry = () => {
|
||||
uni.navigateTo({ url: '/pages/case/patient-invite' });
|
||||
uni.navigateTo({ url: '/pages/work/team/invite/invite-patient' });
|
||||
};
|
||||
|
||||
const openCreatePatientEntry = () => {
|
||||
|
||||
@ -60,9 +60,9 @@
|
||||
class="send-btn"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click.stop="sendArticle(article)"
|
||||
@click.stop="handlePrimaryAction(article)"
|
||||
>
|
||||
发送
|
||||
{{ isSelectMode ? '选择' : '发送' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
@ -111,6 +111,7 @@ import { ref, onMounted } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import api from "@/utils/api.js";
|
||||
import useAccountStore from "@/store/account.js";
|
||||
import { sendArticleMessage } from "@/utils/send-message-helper.js";
|
||||
import EmptyData from "@/components/empty-data.vue";
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
@ -124,6 +125,9 @@ const pageParams = ref({
|
||||
corpId: "",
|
||||
});
|
||||
|
||||
const isSelectMode = ref(false);
|
||||
const selectEventName = ref("");
|
||||
|
||||
// 搜索关键词
|
||||
const searchTitle = ref("");
|
||||
let searchTimer = null;
|
||||
@ -311,74 +315,44 @@ const closePreview = () => {
|
||||
previewPopup.value?.close();
|
||||
};
|
||||
|
||||
const selectArticle = (article) => {
|
||||
if (!selectEventName.value) {
|
||||
uni.showToast({ title: "缺少 eventName", icon: "none" });
|
||||
return;
|
||||
}
|
||||
uni.$emit(selectEventName.value, article);
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const handlePrimaryAction = (article) => {
|
||||
if (isSelectMode.value) return selectArticle(article);
|
||||
return sendArticle(article);
|
||||
};
|
||||
|
||||
// 发送文章
|
||||
const sendArticle = async (article) => {
|
||||
try {
|
||||
const { doctorInfo } = useAccountStore();
|
||||
|
||||
// 1. 调用后端API记录发送
|
||||
const result = await api("sendArticleMessage", {
|
||||
groupId: pageParams.value.groupId,
|
||||
fromAccount: doctorInfo.userid,
|
||||
articleId: article._id,
|
||||
title: article.title || "宣教文章",
|
||||
imgUrl: article.cover || "",
|
||||
desc: "点击查看详情",
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
// 2. 通过IM系统发送自定义消息到聊天列表
|
||||
try {
|
||||
// 获取全局IM管理器
|
||||
const { globalTimChatManager } = await import("@/utils/tim-chat.js");
|
||||
|
||||
if (globalTimChatManager && globalTimChatManager.tim && globalTimChatManager.isLoggedIn) {
|
||||
// 重要:设置当前会话ID,确保消息发送到正确的群聊
|
||||
const conversationID = `GROUP${pageParams.value.groupId}`;
|
||||
globalTimChatManager.currentConversationID = conversationID;
|
||||
|
||||
console.log("设置当前会话ID:", conversationID);
|
||||
|
||||
// 构建自定义消息数据
|
||||
const customMessageData = {
|
||||
messageType: "article",
|
||||
title: article.title || "宣教文章",
|
||||
articleId: article._id,
|
||||
cover: article.cover || "",
|
||||
desc: "点击查看详情",
|
||||
content: article.title || "宣教文章"
|
||||
};
|
||||
|
||||
// 发送自定义消息
|
||||
const sendResult = await globalTimChatManager.sendCustomMessage(customMessageData);
|
||||
if (sendResult && sendResult.success) {
|
||||
console.log("✓ 文章消息已通过IM系统发送");
|
||||
} else {
|
||||
console.warn("⚠️ 文章消息发送失败:", sendResult?.error);
|
||||
}
|
||||
} else {
|
||||
console.warn("⚠️ IM系统未就绪,消息可能不会显示在聊天列表");
|
||||
}
|
||||
} catch (imError) {
|
||||
console.error("通过IM系统发送消息失败:", imError);
|
||||
// IM发送失败不影响后端记录,继续返回
|
||||
}
|
||||
|
||||
// 3. 记录文章发送记录
|
||||
try {
|
||||
await api("addArticleSendRecord", {
|
||||
articleId: article._id,
|
||||
userId: doctorInfo.userid,
|
||||
customerId: pageParams.value.patientId,
|
||||
corpId: corpId,
|
||||
});
|
||||
} catch (recordError) {
|
||||
console.error("记录文章发送失败:", recordError);
|
||||
// 使用统一的消息发送助手
|
||||
const success = await sendArticleMessage(
|
||||
{
|
||||
_id: article._id,
|
||||
title: article.title || "宣教文章",
|
||||
cover: article.cover || "",
|
||||
url: article.url || "",
|
||||
subtitle: article.subtitle || "",
|
||||
},
|
||||
{
|
||||
articleId: article._id,
|
||||
userId: doctorInfo?.userid,
|
||||
customerId: pageParams.value.patientId,
|
||||
corpId: corpId,
|
||||
}
|
||||
);
|
||||
|
||||
if (success) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
throw new Error(result.message || "发送失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("发送文章失败:", error);
|
||||
@ -398,6 +372,8 @@ const goBack = () => {
|
||||
|
||||
// 页面加载时接收参数
|
||||
onLoad((options) => {
|
||||
isSelectMode.value = String(options?.select || '') === '1';
|
||||
selectEventName.value = String(options?.eventName || '');
|
||||
if (options.groupId) {
|
||||
pageParams.value.groupId = options.groupId;
|
||||
}
|
||||
|
||||
@ -78,10 +78,11 @@ const props = defineProps({
|
||||
userId: { type: String, default: "" },
|
||||
patientId: { type: String, default: "" },
|
||||
corpId: { type: String, default: "" },
|
||||
orderStatus: { type: String, default: "" },
|
||||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["messageSent", "scrollToBottom", "endConsult"]);
|
||||
const emit = defineEmits(["messageSent", "scrollToBottom", "endConsult", "openConsult"]);
|
||||
|
||||
// 输入相关状态
|
||||
const inputText = ref("");
|
||||
@ -404,34 +405,68 @@ const handleEndConsult = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const morePanelButtons = [
|
||||
{ text: "照片", icon: "/static/icon/zhaopian.png", action: showImagePicker },
|
||||
{
|
||||
text: "回访任务",
|
||||
icon: "/static/icon/huifangrenwu.png",
|
||||
action: showFollowUpTasks,
|
||||
},
|
||||
{
|
||||
text: "常用语",
|
||||
icon: "/static/icon/changyongyu.png",
|
||||
action: goToCommonPhrases,
|
||||
},
|
||||
{
|
||||
text: "宣教",
|
||||
icon: "/static/icon/xuanjiaowenzhang.png",
|
||||
action: goToArticleList,
|
||||
},
|
||||
{
|
||||
text: "问卷",
|
||||
icon: "/static/icon/wenjuan.png",
|
||||
action: goToSurveyList,
|
||||
},
|
||||
{
|
||||
text: "结束问诊",
|
||||
icon: "/static/icon/jieshuzixun.png",
|
||||
action: handleEndConsult,
|
||||
},
|
||||
];
|
||||
// 开启会话
|
||||
const handleOpenConsult = () => {
|
||||
uni.showModal({
|
||||
title: "确认开启会话",
|
||||
content: "确定要重新开启本次会话吗?",
|
||||
confirmText: "确定开启",
|
||||
cancelText: "取消",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 关闭功能面板
|
||||
showMorePanel.value = false;
|
||||
// 触发父组件的开启会话事件
|
||||
emit("openConsult");
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const morePanelButtons = computed(() => {
|
||||
const buttons = [
|
||||
{ text: "照片", icon: "/static/icon/zhaopian.png", action: showImagePicker },
|
||||
{
|
||||
text: "回访任务",
|
||||
icon: "/static/icon/huifangrenwu.png",
|
||||
action: showFollowUpTasks,
|
||||
},
|
||||
{
|
||||
text: "常用语",
|
||||
icon: "/static/icon/changyongyu.png",
|
||||
action: goToCommonPhrases,
|
||||
},
|
||||
{
|
||||
text: "宣教",
|
||||
icon: "/static/icon/xuanjiaowenzhang.png",
|
||||
action: goToArticleList,
|
||||
},
|
||||
{
|
||||
text: "问卷",
|
||||
icon: "/static/icon/wenjuan.png",
|
||||
action: goToSurveyList,
|
||||
},
|
||||
];
|
||||
|
||||
// 根据订单状态显示不同的按钮
|
||||
if (props.orderStatus === "finished") {
|
||||
// 已结束状态:显示"开启会话"按钮
|
||||
buttons.push({
|
||||
text: "开启会话",
|
||||
icon: "/static/icon/kaiqihuihua.png",
|
||||
action: handleOpenConsult,
|
||||
});
|
||||
} else {
|
||||
// 处理中状态:显示"结束问诊"按钮
|
||||
buttons.push({
|
||||
text: "结束问诊",
|
||||
icon: "/static/icon/jieshuzixun.png",
|
||||
action: handleEndConsult,
|
||||
});
|
||||
}
|
||||
|
||||
return buttons;
|
||||
});
|
||||
|
||||
function handleInputFocus() {
|
||||
console.log("handleInputFocus");
|
||||
|
||||
@ -75,6 +75,8 @@ const text = computed(() => {
|
||||
return '问诊已结束';
|
||||
case 'consult_timeout':
|
||||
return '问诊已超时';
|
||||
case 'consult_reopened':
|
||||
return '会话已重新开启';
|
||||
default:
|
||||
return systemMessageData.value.content || '[系统消息]';
|
||||
}
|
||||
|
||||
@ -163,9 +163,11 @@
|
||||
:patientId="patientId"
|
||||
:corpId="corpId"
|
||||
:patientInfo="patientInfo"
|
||||
:orderStatus="orderStatus"
|
||||
@scrollToBottom="() => scrollToBottom(true)"
|
||||
@messageSent="() => scrollToBottom(true)"
|
||||
@endConsult="handleEndConsult"
|
||||
@openConsult="handleOpenConsult"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
@ -962,6 +964,43 @@ const handleEndConsult = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理开启会话
|
||||
const handleOpenConsult = async () => {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: "处理中...",
|
||||
});
|
||||
// 调用开启会话接口
|
||||
const result = await api("openConsultation", {
|
||||
groupId: groupId.value,
|
||||
adminAccount: account.value?.userId || "",
|
||||
extraData: {
|
||||
openedBy: account.value?.userId || "",
|
||||
openedByName: account.value?.name || "医生",
|
||||
openReason: "重新开启会话",
|
||||
},
|
||||
});
|
||||
uni.hideLoading();
|
||||
if (result.success) {
|
||||
// 重新获取订单状态
|
||||
await fetchGroupOrderStatus();
|
||||
uni.showToast({
|
||||
title: "会话已开启",
|
||||
icon: "success",
|
||||
});
|
||||
} else {
|
||||
throw new Error(result.message || "操作失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("开启会话失败:", error);
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: error.message || "操作失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 页面卸载
|
||||
onUnmounted(() => {
|
||||
clearMessageCache();
|
||||
|
||||
@ -63,9 +63,9 @@
|
||||
class="send-btn"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="sendSurvey(survey)"
|
||||
@click="handlePrimaryAction(survey)"
|
||||
>
|
||||
发送
|
||||
{{ isSelectMode ? '选择' : '发送' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
@ -90,6 +90,7 @@ import { ref, onMounted } from "vue";
|
||||
import api from "@/utils/api.js";
|
||||
import useAccountStore from "@/store/account.js";
|
||||
import { globalTimChatManager } from "@/utils/tim-chat.js";
|
||||
import { sendSurveyMessage } from "@/utils/send-message-helper.js";
|
||||
import EmptyData from "@/components/empty-data.vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
const env = __VITE_ENV__;
|
||||
@ -118,14 +119,33 @@ const pageSize = 30;
|
||||
const total = ref(0);
|
||||
const emptyText = ref("");
|
||||
|
||||
const isSelectMode = ref(false);
|
||||
const selectEventName = ref("");
|
||||
|
||||
// 页面加载时接收参数
|
||||
onLoad((options) => {
|
||||
isSelectMode.value = String(options?.select || '') === '1';
|
||||
selectEventName.value = String(options?.eventName || '');
|
||||
customerId.value = options?.patientId || "";
|
||||
customerName.value = options?.customerName || "";
|
||||
getCategoryList();
|
||||
loadSurveyList();
|
||||
});
|
||||
|
||||
const selectSurvey = (survey) => {
|
||||
if (!selectEventName.value) {
|
||||
uni.showToast({ title: "缺少 eventName", icon: "none" });
|
||||
return;
|
||||
}
|
||||
uni.$emit(selectEventName.value, survey);
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const handlePrimaryAction = (survey) => {
|
||||
if (isSelectMode.value) return selectSurvey(survey);
|
||||
return sendSurvey(survey);
|
||||
};
|
||||
|
||||
// 获取分类列表
|
||||
const getCategoryList = async () => {
|
||||
try {
|
||||
@ -315,48 +335,23 @@ const sendSurvey = async (survey) => {
|
||||
const doctorInfo = accountStore.doctorInfo;
|
||||
userId.value = doctorInfo?.userid;
|
||||
|
||||
// 生成发送ID
|
||||
const sendSurveyId = generateRandomString(10);
|
||||
|
||||
// 创建问卷记录
|
||||
const createRecordRes = await api("createSurveyRecord", {
|
||||
corpId: corpId,
|
||||
userId: userId.value,
|
||||
surveryId: survey._id,
|
||||
memberId: customerId.value,
|
||||
customer: customerName.value,
|
||||
sendSurveyId: sendSurveyId,
|
||||
});
|
||||
|
||||
if (!createRecordRes.success) {
|
||||
uni.showToast({
|
||||
title: createRecordRes.message || "创建问卷记录失败",
|
||||
icon: "none",
|
||||
});
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const answerId = createRecordRes?.id || "";
|
||||
|
||||
// 生成发送链接
|
||||
const sendLink = generateSendLink(
|
||||
survey,
|
||||
answerId,
|
||||
customerId.value,
|
||||
customerName.value,
|
||||
sendSurveyId
|
||||
// 使用统一的消息发送助手
|
||||
const success = await sendSurveyMessage(
|
||||
{
|
||||
_id: survey._id,
|
||||
name: survey.name || "问卷",
|
||||
surveryId: survey.surveryId || survey._id,
|
||||
url: survey.url || "",
|
||||
},
|
||||
{
|
||||
userId: userId.value,
|
||||
customerId: customerId.value,
|
||||
customerName: customerName.value,
|
||||
corpId: corpId,
|
||||
}
|
||||
);
|
||||
|
||||
// 构建自定义消息
|
||||
const customMessage = buildSurveyMessage(survey, sendLink);
|
||||
|
||||
// 发送自定义消息到IM
|
||||
const result = await timChatManager.sendCustomMessage(
|
||||
customMessage,
|
||||
"SURVEY"
|
||||
);
|
||||
if (result.success) {
|
||||
if (success) {
|
||||
uni.showToast({
|
||||
title: "发送成功",
|
||||
icon: "success",
|
||||
@ -365,8 +360,6 @@ const sendSurvey = async (survey) => {
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 500);
|
||||
} else {
|
||||
throw new Error(result.error || "发送失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("发送问卷失败:", error);
|
||||
|
||||
@ -71,7 +71,7 @@
|
||||
<form-textarea
|
||||
name="个人介绍"
|
||||
:form="formData"
|
||||
title="intro"
|
||||
title="memberTroduce"
|
||||
:word-limit="500"
|
||||
@change="handleFieldChange"
|
||||
/>
|
||||
@ -110,7 +110,7 @@ const formData = ref({
|
||||
department: "",
|
||||
departmentName: "",
|
||||
departmentId: "",
|
||||
intro: "",
|
||||
memberTroduce: "",
|
||||
});
|
||||
|
||||
// 选项数据
|
||||
|
||||
@ -47,7 +47,7 @@ export default [
|
||||
},
|
||||
{
|
||||
path: 'pages/home/case-home',
|
||||
meta: { title: '病例', login: false },
|
||||
meta: { title: '病历', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/search',
|
||||
@ -65,10 +65,6 @@ export default [
|
||||
path: 'pages/case/batch-share',
|
||||
meta: { title: '共享客户', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/patient-invite',
|
||||
meta: { title: '邀请患者', login: false },
|
||||
},
|
||||
{
|
||||
path: 'pages/case/patient-create',
|
||||
meta: { title: '新增患者', login: false },
|
||||
|
||||
@ -94,6 +94,7 @@ const urlsConfig = {
|
||||
getChatRecordsByGroupId: "getChatRecordsByGroupId",
|
||||
sendConsultRejectedMessage: "sendConsultRejectedMessage",
|
||||
endConsultation: "endConsultation",
|
||||
openConsultation: "openConsultation",
|
||||
getGroupListByGroupId: "getGroupListByGroupId",
|
||||
acceptConsultation: "acceptConsultation",
|
||||
sendArticleMessage: "sendArticleMessage",
|
||||
|
||||
330
utils/send-message-helper.js
Normal file
330
utils/send-message-helper.js
Normal file
@ -0,0 +1,330 @@
|
||||
/**
|
||||
* 消息发送助手 - 统一处理不同类型消息的发送
|
||||
*/
|
||||
|
||||
import { globalTimChatManager } from './tim-chat.js';
|
||||
import api from './api.js';
|
||||
import { toast } from './widget.js';
|
||||
|
||||
/**
|
||||
* 发送文字消息
|
||||
* @param {string} content - 文字内容
|
||||
* @returns {Promise<boolean>} 发送是否成功
|
||||
*/
|
||||
export async function sendTextMessage(content) {
|
||||
if (!content || !content.trim()) {
|
||||
toast('文字内容不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!globalTimChatManager?.isLoggedIn) {
|
||||
toast('IM系统未就绪,请稍后重试');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 直接调用 tim-chat 的 sendTextMessage 方法
|
||||
const result = await globalTimChatManager.sendTextMessage(content.trim());
|
||||
|
||||
if (result?.success) {
|
||||
return true;
|
||||
} else {
|
||||
toast(result?.error || '发送文字消息失败');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送文字消息异常:', error);
|
||||
toast('发送文字消息失败');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送图片消息
|
||||
* @param {string} imageUrl - 图片URL(字符串)
|
||||
* @param {string} imageName - 图片名称(可选)
|
||||
* @returns {Promise<boolean>} 发送是否成功
|
||||
*/
|
||||
export async function sendImageMessage(imageUrl, imageName = '图片') {
|
||||
if (!imageUrl) {
|
||||
toast('图片URL不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!globalTimChatManager?.isLoggedIn) {
|
||||
toast('IM系统未就绪,请稍后重试');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 直接调用 tim-chat 的 sendImageMessage 方法
|
||||
// tim-chat.js 中的 getImageUrl 方法可以处理 URL 字符串
|
||||
const result = await globalTimChatManager.sendImageMessage(imageUrl);
|
||||
|
||||
if (result?.success) {
|
||||
return true;
|
||||
} else {
|
||||
toast(result?.error || '发送图片消息失败');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送图片消息异常:', error);
|
||||
toast('发送图片消息失败');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送宣教文章消息
|
||||
* @param {Object} article - 文章对象 { _id, title, cover, url }
|
||||
* @param {Object} options - 额外选项 { groupId, patientId, corpId }
|
||||
* @returns {Promise<boolean>} 发送是否成功
|
||||
*/
|
||||
export async function sendArticleMessage(article, options = {}) {
|
||||
if (!article || !article._id) {
|
||||
toast('文章信息不完整');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!globalTimChatManager?.isLoggedIn) {
|
||||
toast('IM系统未就绪,请稍后重试');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 构建自定义消息 - 直接传递对象,sendCustomMessage会处理JSON序列化
|
||||
const customMessageData = {
|
||||
type: 'article',
|
||||
title: article.title || '宣教文章',
|
||||
articleId: article._id,
|
||||
cover: article.cover || '',
|
||||
desc: article.subtitle || '点击查看详情',
|
||||
url: article.url || '',
|
||||
messageType: 'article',
|
||||
};
|
||||
|
||||
// 发送自定义消息
|
||||
const result = await globalTimChatManager.sendCustomMessage(customMessageData);
|
||||
|
||||
if (result?.success) {
|
||||
// 记录文章发送记录(异步,不阻塞)
|
||||
if (options.articleId && options.userId && options.customerId && options.corpId) {
|
||||
api('addArticleSendRecord', {
|
||||
articleId: options.articleId,
|
||||
userId: options.userId,
|
||||
customerId: options.customerId,
|
||||
corpId: options.corpId,
|
||||
}).catch((err) => {
|
||||
console.error('记录文章发送失败:', err);
|
||||
});
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
toast(result?.error || '发送文章消息失败');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送文章消息异常:', error);
|
||||
toast('发送文章消息失败');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送问卷消息
|
||||
* @param {Object} survey - 问卷对象 { _id, name, surveryId, url, createBy }
|
||||
* @param {Object} options - 额外选项 { userId, customerId, customerName, corpId, env }
|
||||
* @returns {Promise<boolean>} 发送是否成功
|
||||
*/
|
||||
export async function sendSurveyMessage(survey, options = {}) {
|
||||
if (!survey || !survey._id) {
|
||||
toast('问卷信息不完整');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!globalTimChatManager?.isLoggedIn) {
|
||||
toast('IM系统未就绪,请稍后重试');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 生成发送ID
|
||||
const sendSurveyId = generateRandomString(10);
|
||||
|
||||
// 创建问卷记录
|
||||
const createRecordRes = await api('createSurveyRecord', {
|
||||
corpId: options.corpId,
|
||||
userId: options.userId,
|
||||
surveryId: survey._id,
|
||||
memberId: options.customerId,
|
||||
customer: options.customerName,
|
||||
sendSurveyId: sendSurveyId,
|
||||
});
|
||||
|
||||
if (!createRecordRes?.success) {
|
||||
toast(createRecordRes?.message || '创建问卷记录失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
const answerId = createRecordRes?.id || '';
|
||||
|
||||
// 生成问卷链接
|
||||
const surveyLink = generateSendLink(
|
||||
survey,
|
||||
answerId,
|
||||
options.customerId,
|
||||
options.customerName,
|
||||
sendSurveyId,
|
||||
{
|
||||
corpId: options.corpId,
|
||||
userId: options.userId,
|
||||
env: options.env,
|
||||
}
|
||||
);
|
||||
|
||||
// 构建自定义消息
|
||||
const customMessageData = buildSurveyMessage(survey, surveyLink);
|
||||
|
||||
// 发送自定义消息 - 直接传递消息对象,不再进行额外序列化
|
||||
const result = await globalTimChatManager.sendCustomMessage(customMessageData);
|
||||
|
||||
if (result?.success) {
|
||||
return true;
|
||||
} else {
|
||||
toast(result?.error || '发送问卷消息失败');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送问卷消息异常:', error);
|
||||
toast('发送问卷消息失败');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串
|
||||
* @param {number} length - 字符串长度
|
||||
* @returns {string} 随机字符串
|
||||
*/
|
||||
function 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成问卷发送链接
|
||||
* @param {Object} survey - 问卷对象
|
||||
* @param {string} answerId - 答卷ID
|
||||
* @param {string} customerId - 客户ID
|
||||
* @param {string} customerName - 客户名称
|
||||
* @param {string} sendSurveyId - 发送问卷ID
|
||||
* @param {Object} context - 上下文信息 { corpId, userId, env }
|
||||
* @returns {string} 问卷链接
|
||||
*/
|
||||
function generateSendLink(survey, answerId, customerId, customerName, sendSurveyId, context = {}) {
|
||||
const { corpId, userId, env } = context;
|
||||
const isSystem = survey.createBy === 'system';
|
||||
let url = '';
|
||||
|
||||
if (isSystem) {
|
||||
// 系统问卷:使用 VITE_SURVEY_URL
|
||||
url = `${env?.MP_SURVEY_URL}?corpId=${corpId}&surveryId=${survey.surveryId}&memberId=${customerId}&sendSurveyId=${sendSurveyId}&userId=${userId}`;
|
||||
} else {
|
||||
// 自定义问卷:使用 VITE_PATIENT_PAGE_BASE_URL
|
||||
url = `${env?.MP_PATIENT_PAGE_BASE_URL}pages/survery/fill?corpId=${corpId}&surveryId=${survey._id}&memberId=${customerId}&unionid=unionid&answerId=${answerId}&name=${customerName || ''}`;
|
||||
}
|
||||
|
||||
// 如果已有链接,直接使用并拼接额外参数
|
||||
if (survey.url && survey.url.trim()) {
|
||||
const separator = survey.url.includes('?') ? '&' : '?';
|
||||
const params = [];
|
||||
if (answerId) params.push(`answerId=${answerId}`);
|
||||
if (sendSurveyId) params.push(`sendSurveyId=${sendSurveyId}`);
|
||||
if (userId) params.push(`userId=${userId}`);
|
||||
if (customerId) params.push(`memberId=${customerId}`);
|
||||
|
||||
if (params.length > 0) {
|
||||
url = `${survey.url}${separator}${params.join('&')}`;
|
||||
} else {
|
||||
url = survey.url;
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建问卷自定义消息
|
||||
* @param {Object} survey - 问卷对象
|
||||
* @param {string} surveyLink - 问卷链接
|
||||
* @returns {Object} 自定义消息对象
|
||||
*/
|
||||
function buildSurveyMessage(survey, surveyLink) {
|
||||
return {
|
||||
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',
|
||||
messageType: 'survey',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理回访任务消息发送
|
||||
* @param {Object} messages - 消息数组
|
||||
* @param {Object} context - 上下文信息 { userId, customerId, customerName, corpId }
|
||||
* @returns {Promise<boolean>} 是否全部发送成功
|
||||
*/
|
||||
export async function handleFollowUpMessages(messages, context = {}) {
|
||||
if (!Array.isArray(messages) || messages.length === 0) {
|
||||
toast('没有要发送的消息');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
let allSuccess = true;
|
||||
|
||||
for (const msg of messages) {
|
||||
let success = false;
|
||||
|
||||
if (msg.type === 'text') {
|
||||
success = await sendTextMessage(msg.content);
|
||||
} else if (msg.type === 'image') {
|
||||
success = await sendImageMessage(msg.content, msg.name);
|
||||
} else if (msg.type === 'article') {
|
||||
success = await sendArticleMessage(msg.content, {
|
||||
articleId: msg.content.articleId,
|
||||
userId: context.userId,
|
||||
customerId: context.customerId,
|
||||
corpId: context.corpId,
|
||||
});
|
||||
} else if (msg.type === 'questionnaire') {
|
||||
success = await sendSurveyMessage(msg.content, {
|
||||
userId: context.userId,
|
||||
customerId: context.customerId,
|
||||
customerName: context.customerName,
|
||||
corpId: context.corpId,
|
||||
env: context.env,
|
||||
});
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
allSuccess = false;
|
||||
// 继续发送其他消息,不中断
|
||||
}
|
||||
}
|
||||
|
||||
return allSuccess;
|
||||
} catch (error) {
|
||||
console.error('处理回访任务消息异常:', error);
|
||||
toast('消息发送过程中出现错误');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -911,7 +911,7 @@ class TimChatManager {
|
||||
uni.removeStorageSync('account')
|
||||
uni.removeStorageSync('openid')
|
||||
uni.reLaunch({
|
||||
url: '/pages-center/login/login'
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -1153,6 +1153,9 @@ class TimChatManager {
|
||||
case "consult_ended":
|
||||
lastMessageText = '已结束当前会话'
|
||||
break
|
||||
case "consult_reopened":
|
||||
lastMessageText = '会话已重新开启'
|
||||
break
|
||||
default:
|
||||
lastMessageText = '[自定义消息]'
|
||||
}
|
||||
@ -2580,6 +2583,9 @@ class TimChatManager {
|
||||
case 'consult_ended':
|
||||
messageText = '已结束当前会话'
|
||||
break
|
||||
case 'consult_reopened':
|
||||
messageText = '会话已重新开启'
|
||||
break
|
||||
default:
|
||||
messageText = customData.content || '[自定义消息]'
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user