From 70d0f5e4965c3fa2a53c0054ae4c3559b6f14dfd Mon Sep 17 00:00:00 2001
From: Jafeng <2998840497@qq.com>
Date: Mon, 26 Jan 2026 15:39:14 +0800
Subject: [PATCH] =?UTF-8?q?feat:=E5=9B=9E=E8=AE=BF=E6=8E=A5=E5=8F=A3?=
=?UTF-8?q?=E6=8E=A5=E5=85=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../archive-detail/follow-up-manage-tab.vue | 210 ++++-
components/manage-plan/node-list.vue | 54 ++
components/manage-plan/plan-node-item.vue | 113 +++
components/manage-plan/plan-node-list.vue | 128 ++++
pages.json | 18 +-
pages/case/followup-detail.vue | 719 ++++++++++++------
pages/case/new-followup-record.vue | 249 ++++--
pages/case/new-followup.vue | 325 +++++---
pages/case/plan-execute.vue | 400 ++++++++++
pages/case/plan-list.vue | 133 +++-
pages/case/plan-preview.vue | 104 +++
routes/index.js | 12 +-
scripts/pre-build.js | 9 +-
utils/api.js | 13 +
utils/todo-const.js | 30 +
15 files changed, 2061 insertions(+), 456 deletions(-)
create mode 100644 components/manage-plan/node-list.vue
create mode 100644 components/manage-plan/plan-node-item.vue
create mode 100644 components/manage-plan/plan-node-list.vue
create mode 100644 pages/case/plan-execute.vue
create mode 100644 pages/case/plan-preview.vue
create mode 100644 utils/todo-const.js
diff --git a/components/archive-detail/follow-up-manage-tab.vue b/components/archive-detail/follow-up-manage-tab.vue
index 7c40e6f..8babfb0 100644
--- a/components/archive-detail/follow-up-manage-tab.vue
+++ b/components/archive-detail/follow-up-manage-tab.vue
@@ -67,6 +67,21 @@
+
+ 任务状态
+
+
+ {{ t.label }}
+
+
+
+
任务类型
@@ -119,7 +134,12 @@
+
+
+
diff --git a/components/manage-plan/plan-node-item.vue b/components/manage-plan/plan-node-item.vue
new file mode 100644
index 0000000..2034e5b
--- /dev/null
+++ b/components/manage-plan/plan-node-item.vue
@@ -0,0 +1,113 @@
+
+
+
+ 待办类型:
+ {{ getTodoEventTypeLabel(item?.eventType) }}
+
+
+ 任务内容:
+
+ {{ item.taskContent }}
+
+ 暂无内容
+
+
+ 向客户发送:
+
+ 【提醒】
+ {{ item.sendContent }}
+
+
+ 【{{ sendFile.fileLabel }}】
+ {{ sendFile.name }}
+
+ 暂无发送内容
+
+
+
+
+
+
+
+
diff --git a/components/manage-plan/plan-node-list.vue b/components/manage-plan/plan-node-list.vue
new file mode 100644
index 0000000..f4f5769
--- /dev/null
+++ b/components/manage-plan/plan-node-list.vue
@@ -0,0 +1,128 @@
+
+
+
+
+ {{ g.title }}
+
+ {{ shrinkMap[g.id] ? '展开' : '收起' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages.json b/pages.json
index 81c291c..55f13f8 100644
--- a/pages.json
+++ b/pages.json
@@ -19,12 +19,6 @@
"navigationStyle": "custom"
}
},
- // {
- // "path": "pages/message/index",
- // "style": {
- // "navigationBarTitleText": "消息"
- // }
- // },
{
"path": "pages/message/index",
"style": {
@@ -134,6 +128,18 @@
"navigationBarTitleText": "回访计划"
}
},
+ {
+ "path": "pages/case/plan-preview",
+ "style": {
+ "navigationBarTitleText": "回访计划详情"
+ }
+ },
+ {
+ "path": "pages/case/plan-execute",
+ "style": {
+ "navigationBarTitleText": "执行回访计划"
+ }
+ },
{
"path": "pages/work/work",
"style": {
diff --git a/pages/case/followup-detail.vue b/pages/case/followup-detail.vue
index d49e0ea..14e6f1f 100644
--- a/pages/case/followup-detail.vue
+++ b/pages/case/followup-detail.vue
@@ -1,51 +1,87 @@
-
-
-
- {{ currentType.label }}
- {{ currentStatus.label }}
+
+
+
+
+ {{ currentType.label }}
+ {{ currentStatus.label }}
+
+ {{ form.taskContent || '暂无任务内容' }}
- {{ form.taskContent || '暂无任务内容' }}
-
- 计划执行时间:{{ planDate || '--' }}
- 执行人:{{ form.executorName || '--' }}
-
-
-
- 回访方式
-
-
- 电话
-
- {{ phone || '选择号码' }}
-
+
+ 发送内容 :
+ {{ todo.sendContent }}
+
+ {{ f.typeStr }}
+ {{ f.name }}
-
-
- 微信
- (mock:不接入会话)
+
+
+ 计划执行时间:{{ planDate || '--' }}
+ {{ executorDisplay || '--' }}
-
-
-
- 回访结果
-
-
- {{ (form.result || '').length }}/500
+
+ 客户:
+ {{ customerDisplay || '--' }}
+
+
+ 回访方式
+
+
+ 电话
+
+ {{ phone || '请选择号码' }}
+
+
+
+
+
+ 微信
+
+
+
+
+ 回访结果
+
+
+
+
+
+
+
+ 创建人 :
+ {{ creatorDisplay || '--' }}
+
+
+ 创建时间 :
+ {{ createTimeStr || '--' }}
+
+
+ 执行时间 :
+ {{ endTimeStr }}
+
+
+
+
+
+
+
-
-
-
+
@@ -55,36 +91,90 @@
import { computed, reactive, ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import dayjs from 'dayjs';
-import { ensureSeed, getFollowup, removeFollowup, upsertFollowup } from '@/components/archive-detail/mock';
+import { storeToRefs } from 'pinia';
+import api from '@/utils/api';
+import useAccountStore from '@/store/account';
+import { toast } from '@/utils/widget';
+import { getTodoEventTypeLabel } from '@/utils/todo-const';
const archiveId = ref('');
const mode = ref('add');
const taskId = ref('');
-const typeOptions = [
- { label: '回访', value: 'followup' },
- { label: '复诊提醒', value: 'revisit' },
- { label: '问卷', value: 'questionnaire' },
- { label: '其他', value: 'other' },
-];
-const currentType = ref(typeOptions[0]);
+const todo = ref(null);
+const currentType = computed(() => ({ label: getTodoEventTypeLabel(todo.value?.eventType), value: String(todo.value?.eventType || '') }));
+const customer = ref(null);
+const userNameMap = ref({});
-const statusOptions = [
- { label: '待处理', value: 'processing' },
- { label: '未开始', value: 'notStart' },
- { label: '已完成', value: 'treated' },
- { label: '已取消', value: 'cancelled' },
- { label: '已过期', value: 'expired' },
-];
-const currentStatus = ref(statusOptions[0]);
+function statusLabelFromStatus(status) {
+ const map = {
+ processing: '待处理',
+ notStart: '未开始',
+ treated: '已完成',
+ cancelled: '已取消',
+ expired: '已过期',
+ };
+ return map[status] || '未知';
+}
-const teamOptions = [
- { label: '口腔一科(示例)', value: 'team_1' },
- { label: '正畸团队(示例)', value: 'team_2' },
-];
-const currentTeam = ref(teamOptions[0]);
+function getStatus(t) {
+ const endOfToday = dayjs().endOf('day').valueOf();
+ const startOfToday = dayjs().startOf('day').valueOf();
+ const plannedExecutionTime = Number(t?.plannedExecutionTime || 0) || 0;
+ const expireTime = Number(t?.expireTime || 0) || 0;
+ const eventStatus = String(t?.eventStatus || '');
+
+ if (eventStatus === 'treated') return 'treated';
+ if (eventStatus === 'closed') return 'cancelled';
+ if (eventStatus === 'expire') return 'expired';
+
+ if (eventStatus === 'untreated') {
+ if (expireTime && expireTime < startOfToday) return 'expired';
+ if (plannedExecutionTime >= endOfToday) return 'notStart';
+ if (plannedExecutionTime <= startOfToday && (!expireTime || expireTime >= endOfToday)) return 'processing';
+ return 'processing';
+ }
+ return 'processing';
+}
+
+const currentStatus = computed(() => {
+ const status = getStatus(todo.value);
+ return { value: status, label: statusLabelFromStatus(status) };
+});
+
+const planDate = computed(() => {
+ const v = todo.value?.plannedExecutionTime;
+ return v && dayjs(v).isValid() ? dayjs(v).format('YYYY-MM-DD') : '';
+});
+const createTimeStr = computed(() => {
+ const v = todo.value?.createTime;
+ return v && dayjs(v).isValid() ? dayjs(v).format('YYYY-MM-DD HH:mm') : '';
+});
+const endTimeStr = computed(() => {
+ const v = todo.value?.endTime;
+ return v && dayjs(v).isValid() ? dayjs(v).format('YYYY-MM-DD HH:mm') : '';
+});
+
+const showFileList = computed(() => {
+ const list = Array.isArray(todo.value?.fileList) ? todo.value.fileList : [];
+ return list.map((i) => {
+ const type = String(i?.type || '');
+ const fileType = i?.file && typeof i.file.type === 'string' ? i.file.type : '';
+ let typeStr = '';
+ if (type === 'video' || fileType.includes('video')) typeStr = '【视频】';
+ else if (type === 'image' || fileType.includes('image')) typeStr = '【图片】';
+ else if (fileType === 'article') typeStr = '【文章】';
+ else if (fileType === 'questionnaire') typeStr = '【问卷】';
+ else if (type === 'link') typeStr = '【链接】';
+ const name = i?.file?.name ? String(i.file.name) : '';
+ return { typeStr, name };
+ });
+});
+
+const accountStore = useAccountStore();
+const { account, doctorInfo } = storeToRefs(accountStore);
+const { getDoctorInfo } = accountStore;
-const planDate = ref('');
const form = reactive({
executorName: '',
taskContent: '',
@@ -94,105 +184,166 @@ const form = reactive({
const todoMethod = ref('phone'); // phone | wechat
const phone = ref('');
const mobiles = computed(() => {
- const m = String(form.executorName || '').trim();
- // mock:用本地缓存里的手机号 + 默认号码
const arr = [];
const cached = uni.getStorageSync('ykt_case_archive_detail');
- if (cached && typeof cached === 'object' && cached.mobile) arr.push(String(cached.mobile));
- if (!arr.includes('13800000000')) arr.push('13800000000');
- return arr;
+ if (cached && typeof cached === 'object') {
+ if (cached.mobile) arr.push(String(cached.mobile));
+ if (Array.isArray(cached.mobiles)) arr.push(...cached.mobiles.map(String));
+ }
+ const cleaned = arr.map((i) => String(i || '').trim()).filter(Boolean);
+ if (!cleaned.includes('13800000000')) cleaned.push('13800000000');
+ return Array.from(new Set(cleaned));
});
-const canEdit = computed(() => ['processing', 'notStart'].includes(currentStatus.value.value));
+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;
+ try {
+ await getDoctorInfo();
+ } catch {
+ // ignore
+ }
+}
+
+function resolveUserName(userId) {
+ const id = String(userId || '');
+ if (!id) return '';
+ const map = userNameMap.value || {};
+ return String(map[id] || '') || id;
+}
+
+async function loadUserNameMap(teamId) {
+ if (!teamId) return;
+ const corpId = getCorpId();
+ if (!corpId) return;
+ const res = await api('getTeamData', { corpId, teamId });
+ if (!res?.success) return;
+ const t = res?.data && typeof res.data === 'object' ? res.data : {};
+ const list = Array.isArray(t.memberList) ? t.memberList : [];
+ const map = list.reduce((acc, cur) => {
+ const id = cur?.userid ? String(cur.userid) : '';
+ if (!id) return acc;
+ acc[id] = String(cur?.anotherName || cur?.name || cur?.userid || '');
+ return acc;
+ }, {});
+ userNameMap.value = { ...(userNameMap.value || {}), ...map };
+}
+
+const isSelf = computed(() => {
+ const id = getUserId();
+ return Boolean(id && todo.value && String(todo.value.executorUserId || '') === id);
+});
+const editable = computed(() => isSelf.value && ['notStart', 'processing'].includes(currentStatus.value.value));
+const canEditResult = computed(() => isSelf.value && ['treated', 'cancelled'].includes(currentStatus.value.value));
+
+const canRemove = computed(() => {
+ const t = todo.value;
+ if (!t) return false;
+ const userId = getUserId();
+ return String(t.creatorUserId || '') === userId;
+});
+
+const executorDisplay = computed(() => {
+ const t = todo.value || {};
+ const name = resolveUserName(t.executorUserId);
+ const teamName = String(t.executeTeamName || '');
+ return teamName ? `${name}(${teamName})` : name;
+});
+const creatorDisplay = computed(() => resolveUserName(todo.value?.creatorUserId));
+const customerDisplay = computed(() => {
+ const t = todo.value || {};
+ const c = customer.value || {};
+ return String(t.customerName || c.name || '');
+});
onLoad((options) => {
archiveId.value = options?.archiveId ? String(options.archiveId) : '';
mode.value = options?.mode ? String(options.mode) : 'add';
taskId.value = options?.id ? String(options.id) : '';
- ensureSeed(archiveId.value, {});
+ const cached = uni.getStorageSync('ykt_case_archive_detail');
+ if (cached && typeof cached === 'object') customer.value = cached;
- if (taskId.value) {
- const task = getFollowup({ archiveId: archiveId.value, id: taskId.value });
- if (task) {
- currentType.value = typeOptions.find((i) => i.value === task.eventType) || typeOptions[0];
- currentStatus.value = statusOptions.find((i) => i.value === task.status) || statusOptions[0];
- currentTeam.value = teamOptions.find((i) => i.value === task.executeTeamId) || teamOptions[0];
- planDate.value = task.planDate || '';
- form.executorName = task.executorName || '';
- form.taskContent = task.taskContent || '';
- form.result = task.result || '';
- if (typeof task.todoMethod === 'string') {
- if (task.todoMethod.startsWith('phone')) {
- todoMethod.value = 'phone';
- const parts = task.todoMethod.split(':');
- phone.value = parts[1] || '';
- } else if (task.todoMethod === 'wechat') {
- todoMethod.value = 'wechat';
- phone.value = '';
- }
- }
- return;
- }
+ if (!taskId.value) {
+ toast('缺少回访任务 id');
+ setTimeout(() => uni.navigateBack(), 300);
+ return;
}
-
- planDate.value = dayjs().format('YYYY-MM-DD');
+ getTodo();
});
-function pickType(e) {
- currentType.value = typeOptions[e.detail.value] || typeOptions[0];
-}
-function pickStatus(e) {
- currentStatus.value = statusOptions[e.detail.value] || statusOptions[0];
-}
-function pickTeam(e) {
- currentTeam.value = teamOptions[e.detail.value] || teamOptions[0];
-}
-function pickPlanDate(e) {
- planDate.value = e.detail.value || '';
+function parseTodoMethod(value) {
+ if (typeof value !== 'string') return { todoMethod: '', phone: '' };
+ if (value.startsWith('phone')) {
+ const [, num] = value.split(':');
+ return { todoMethod: 'phone', phone: num || '' };
+ }
+ if (value === 'wechat') return { todoMethod: 'wechat', phone: '' };
+ return { todoMethod: '', phone: '' };
}
function cancel() {
uni.navigateBack();
}
-function save() {
- if (!archiveId.value) {
- uni.showToast({ title: '缺少 archiveId', icon: 'none' });
+function buildTodoMethodValue() {
+ if (todoMethod.value === 'phone') return phone.value ? `phone:${phone.value}` : 'phone';
+ if (todoMethod.value === 'wechat') return 'wechat';
+ return '';
+}
+
+async function getTodo() {
+ await ensureDoctor();
+ const corpId = getCorpId();
+ if (!corpId) {
+ toast('缺少 corpId,请先完成登录/团队选择');
return;
}
- const plannedExecutionTime = planDate.value ? dayjs(planDate.value).valueOf() : Date.now();
- upsertFollowup({
- archiveId: archiveId.value,
- followup: {
- _id: taskId.value || '',
- plannedExecutionTime,
- status: currentStatus.value.value,
- eventStatusLabel: currentStatus.value.label,
- eventType: currentType.value.value,
- eventTypeLabel: currentType.value.label,
- executeTeamId: currentTeam.value.value,
- executeTeamName: currentTeam.value.label,
- executorName: form.executorName || '李医生',
- creatorName: '管理员A',
- taskContent: form.taskContent,
- result: form.result,
- todoMethod: todoMethod.value === 'phone' ? (phone.value ? `phone:${phone.value}` : 'phone') : 'wechat',
- },
- });
- uni.$emit('archive-detail:followup-changed');
- uni.showToast({ title: '保存成功', icon: 'success' });
- setTimeout(() => uni.navigateBack(), 300);
+ const res = await api('getTodoById', { corpId, id: taskId.value });
+ if (!res?.success) {
+ toast(res?.message || '获取回访任务失败');
+ todo.value = null;
+ return;
+ }
+ todo.value = res.data && typeof res.data === 'object' ? res.data : null;
+ await loadUserNameMap(String(todo.value?.executeTeamId || ''));
+
+ const parsed = parseTodoMethod(todo.value?.todoMethod);
+ if (parsed.todoMethod) {
+ todoMethod.value = parsed.todoMethod;
+ phone.value = parsed.phone || '';
+ } else if (editable.value) {
+ todoMethod.value = 'phone';
+ phone.value = mobiles.value[0] || '';
+ }
+
+ form.executorName = resolveUserName(todo.value?.executorUserId);
+ form.taskContent = String(todo.value?.taskContent || '') || '';
+ form.result = String(todo.value?.result || '') || '';
}
function toggleMethod(v) {
- if (!canEdit.value) return;
+ if (!editable.value) return;
todoMethod.value = v;
if (v !== 'phone') phone.value = '';
}
function pickPhone() {
- if (!canEdit.value) return;
+ if (!editable.value) return;
+ if (todoMethod.value !== 'phone') return;
uni.showActionSheet({
itemList: mobiles.value,
success: ({ tapIndex }) => {
@@ -203,41 +354,76 @@ function pickPhone() {
}
function markDone() {
- if (!canEdit.value) return;
+ if (!editable.value) return;
if (!['phone', 'wechat'].includes(todoMethod.value)) return uni.showToast({ title: '请选择回访方式', icon: 'none' });
uni.showModal({
title: '提示',
content: '确定完成该回访任务吗?',
- success: (res) => {
+ success: async (res) => {
if (!res.confirm) return;
- currentStatus.value = statusOptions.find((i) => i.value === 'treated') || currentStatus.value;
- save();
+ await ensureDoctor();
+ const corpId = getCorpId();
+ const userId = getUserId();
+ const methodValue = buildTodoMethodValue();
+ const result = String(form.result || '').trim() || '已完成';
+ const r = await api('setTodoStatus', { corpId, id: taskId.value, eventStatus: 'treated', result, userId, todoMethod: methodValue });
+ if (!r?.success) return toast(r?.message || '操作失败');
+ toast('操作成功');
+ uni.$emit('archive-detail:followup-changed');
+ setTimeout(() => uni.navigateBack(), 300);
},
});
}
function cancelTask() {
- if (!canEdit.value) return;
+ if (!editable.value) return;
uni.showModal({
title: '提示',
content: '确定取消该回访任务吗?',
- success: (res) => {
+ success: async (res) => {
if (!res.confirm) return;
- currentStatus.value = statusOptions.find((i) => i.value === 'cancelled') || currentStatus.value;
- save();
+ await ensureDoctor();
+ const corpId = getCorpId();
+ const userId = getUserId();
+ const methodValue = buildTodoMethodValue();
+ const result = String(form.result || '').trim() || '已取消';
+ const r = await api('setTodoStatus', { corpId, id: taskId.value, eventStatus: 'closed', result, userId, todoMethod: methodValue });
+ if (!r?.success) return toast(r?.message || '操作失败');
+ toast('取消成功');
+ uni.$emit('archive-detail:followup-changed');
+ setTimeout(() => uni.navigateBack(), 300);
},
});
}
+async function save() {
+ if (!canEditResult.value) return;
+ if (String(form.result || '').trim() === '') return toast('请填写回访结果');
+ await ensureDoctor();
+ const corpId = getCorpId();
+ if (!corpId) return toast('缺少 corpId');
+ const methodValue = buildTodoMethodValue();
+ const res = await api('updateTaskTodoResult', { corpId, id: taskId.value, result: String(form.result || ''), todoMethod: methodValue });
+ if (!res?.success) return toast(res?.message || '操作失败');
+ toast('操作成功');
+ uni.$emit('archive-detail:followup-changed');
+ setTimeout(() => uni.navigateBack(), 300);
+}
+
function remove() {
+ if (!canRemove.value) return;
uni.showModal({
title: '提示',
content: '确定删除当前记录?',
- success: (res) => {
+ success: async (res) => {
if (!res.confirm) return;
- removeFollowup({ archiveId: archiveId.value, id: taskId.value });
+ await ensureDoctor();
+ const corpId = getCorpId();
+ const userId = getUserId();
+ const r = await api('removeTodo', { corpId, userId, id: taskId.value });
+ if (!r?.success) return toast(r?.message || '删除失败');
uni.$emit('archive-detail:followup-changed');
- uni.showToast({ title: '已删除', icon: 'success' });
+ toast('已删除');
setTimeout(() => uni.navigateBack(), 300);
},
});
@@ -246,136 +432,183 @@ function remove() {
diff --git a/pages/case/new-followup-record.vue b/pages/case/new-followup-record.vue
index d57daa3..b04a857 100644
--- a/pages/case/new-followup-record.vue
+++ b/pages/case/new-followup-record.vue
@@ -57,6 +57,27 @@
+
+
+
+ 选择回访类型
+
+
+ {{ t.label }}
+
+
+
+
+
+
+
+
@@ -64,11 +85,22 @@
import { computed, reactive, ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import dayjs from 'dayjs';
-import { ensureSeed, upsertFollowup } from '@/components/archive-detail/mock';
+import { storeToRefs } from 'pinia';
+import api from '@/utils/api';
+import useAccountStore from '@/store/account';
+import { toast } from '@/utils/widget';
+import { getTodoEventTypeLabel, getTodoEventTypeOptions } from '@/utils/todo-const';
const archiveId = ref('');
const archiveName = ref('');
const archiveMobile = ref('');
+const customerData = ref({});
+
+const accountStore = useAccountStore();
+const { account, doctorInfo } = storeToRefs(accountStore);
+const { getDoctorInfo } = accountStore;
+
+const teams = ref([]);
const form = reactive({
plannedExecutionTime: '',
@@ -80,18 +112,8 @@ const form = reactive({
result: '',
});
-const eventTypeList = [
- { label: '回访', value: 'followup' },
- { label: '复诊提醒', value: 'revisit' },
- { label: '问卷', value: 'questionnaire' },
- { label: '其他', value: 'other' },
-];
-const eventTypeLabel = computed(() => eventTypeList.find((i) => i.value === form.eventType)?.label || '');
-
-const teamOptions = [
- { label: '口腔一科(示例)', value: 'team_1' },
- { label: '正畸团队(示例)', value: 'team_2' },
-];
+const eventTypeList = getTodoEventTypeOptions();
+const eventTypeLabel = computed(() => getTodoEventTypeLabel(form.eventType));
const mobiles = computed(() => {
const arr = [];
@@ -106,19 +128,84 @@ onLoad((options) => {
if (c && typeof c === 'object') {
archiveId.value = archiveId.value || String(c._id || '');
archiveName.value = String(c.name || '');
+ customerData.value = c;
}
const cached = uni.getStorageSync('ykt_case_archive_detail');
- if (cached && typeof cached === 'object') archiveMobile.value = String(cached.mobile || '');
+ if (cached && typeof cached === 'object' && String(cached?._id || '') === archiveId.value) {
+ archiveMobile.value = String(cached.mobile || '');
+ customerData.value = cached;
+ archiveName.value = archiveName.value || String(cached.name || '');
+ }
if (!archiveId.value) {
uni.showToast({ title: '缺少 archiveId', icon: 'none' });
setTimeout(() => uni.navigateBack(), 300);
return;
}
- ensureSeed(archiveId.value, { name: archiveName.value });
form.plannedExecutionTime = dayjs().format('YYYY-MM-DD');
+ initDefaultTeam();
});
+async function ensureDoctor() {
+ if (doctorInfo.value) return;
+ if (!account.value?.openid) return;
+ try {
+ await getDoctorInfo();
+ } catch {
+ // ignore
+ }
+}
+
+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 || '') || '';
+}
+
+function normalizeTeam(raw) {
+ if (!raw || typeof raw !== 'object') return null;
+ const teamId = raw.teamId || raw.id || raw._id || '';
+ const name = raw.name || raw.teamName || raw.team || '';
+ const corpId = raw.corpId || raw.corpID || '';
+ if (!teamId || !name) return null;
+ return { teamId: String(teamId), name: String(name), corpId: corpId ? String(corpId) : '' };
+}
+
+async function loadTeams() {
+ await ensureDoctor();
+ const corpId = getCorpId();
+ const userId = getUserId();
+ if (!corpId || !userId) return;
+ const res = await api('getTeamBymember', { corpId, corpUserId: userId });
+ if (!res?.success) return;
+ const list = Array.isArray(res?.data) ? res.data : Array.isArray(res?.data?.data) ? res.data.data : [];
+ teams.value = list.map(normalizeTeam).filter(Boolean);
+}
+
+async function initDefaultTeam() {
+ await ensureDoctor();
+ const currentTeam = uni.getStorageSync('ykt_case_current_team') || {};
+ const teamId = String(currentTeam.teamId || '');
+ const teamName = String(currentTeam.name || '');
+ if (teamId) {
+ form.teamId = teamId;
+ form.teamName = teamName;
+ } else {
+ await loadTeams();
+ if (teams.value[0]) {
+ form.teamId = teams.value[0].teamId;
+ form.teamName = teams.value[0].name;
+ }
+ }
+}
+
function changeDate(e) {
form.plannedExecutionTime = e.detail.value || '';
}
@@ -143,23 +230,26 @@ function selectMethod(method) {
}
function selectType() {
- uni.showActionSheet({
- itemList: eventTypeList.map((i) => i.label),
- success: ({ tapIndex }) => {
- form.eventType = eventTypeList[tapIndex]?.value || '';
- },
- });
+ typePopup.value?.open?.();
}
function selectTeam() {
- uni.showActionSheet({
- itemList: teamOptions.map((i) => i.label),
- success: ({ tapIndex }) => {
- const t = teamOptions[tapIndex];
- form.teamId = t.value;
- form.teamName = t.label;
- },
- });
+ (async () => {
+ if (!teams.value.length) await loadTeams();
+ if (!teams.value.length) {
+ toast('暂无可选团队');
+ return;
+ }
+ uni.showActionSheet({
+ itemList: teams.value.map((t) => t.name),
+ success: ({ tapIndex }) => {
+ const t = teams.value[tapIndex];
+ if (!t) return;
+ form.teamId = t.teamId;
+ form.teamName = t.name;
+ },
+ });
+ })();
}
function cancel() {
@@ -170,40 +260,64 @@ function cancel() {
});
}
-function save() {
+async function save() {
if (!form.plannedExecutionTime) return uni.showToast({ title: '请选择回访日期', icon: 'none' });
if (!form.todoMethod) return uni.showToast({ title: '请选择回访方式', icon: 'none' });
if (!form.eventType) return uni.showToast({ title: '请选择回访类型', icon: 'none' });
if (!form.teamId) return uni.showToast({ title: '请选择所在团队', icon: 'none' });
if (!String(form.result || '').trim()) return uni.showToast({ title: '请输入回访结果', icon: 'none' });
+ await ensureDoctor();
+ const corpId = getCorpId();
+ const userId = getUserId();
+ if (!corpId || !userId) {
+ toast('缺少用户/团队信息,请先完成登录与团队选择');
+ return;
+ }
+
+ const customer = customerData.value && typeof customerData.value === 'object' ? customerData.value : {};
+ const customerId = String(customer._id || archiveId.value || '');
+ const customerName = String(customer.name || archiveName.value || '');
+ const customerUserId = String(customer.externalUserId || customer.customerUserId || '') || '';
+
const phoneValue = form.phoneNumber ? `phone:${form.phoneNumber}` : 'phone';
const plannedExecutionTime = dayjs(form.plannedExecutionTime).valueOf();
- upsertFollowup({
- archiveId: archiveId.value,
- followup: {
- plannedExecutionTime,
- endTime: plannedExecutionTime,
- status: 'treated',
- eventStatusLabel: '已完成',
- eventType: form.eventType,
- eventTypeLabel: eventTypeLabel.value || '回访',
- executeTeamId: form.teamId,
- executeTeamName: form.teamName,
- executorName: '我',
- creatorName: '我',
- taskContent: '',
- result: form.result,
- todoMethod: form.todoMethod === 'phone' ? phoneValue : form.todoMethod,
- createTime: Date.now(),
- },
- });
+ const params = {
+ corpId,
+ eventStatus: 'treated',
+ customerId,
+ customerName,
+ customerUserId,
+ executeTeamId: form.teamId,
+ executeTeamName: form.teamName,
+ executorUserId: userId,
+ creatorUserId: userId,
+ taskContent: '',
+ result: String(form.result || ''),
+ todoMethod: form.todoMethod === 'phone' ? phoneValue : form.todoMethod,
+ plannedExecutionTime,
+ endTime: plannedExecutionTime,
+ eventType: form.eventType,
+ };
+ const res = await api('createEvents', { params });
+ if (!res?.success) {
+ toast(res?.message || '保存失败');
+ return;
+ }
uni.$emit('archive-detail:followup-changed');
- uni.showToast({ title: '保存成功', icon: 'success' });
+ toast('保存成功');
setTimeout(() => uni.navigateBack(), 300);
}
+
+const typePopup = ref(null);
+function pickType(v) {
+ form.eventType = String(v || '');
+}
+function closeTypePicker() {
+ typePopup.value?.close?.();
+}
+.picker-sheet {
+ background: #fff;
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
+ overflow: hidden;
+}
+.picker-title {
+ text-align: center;
+ font-size: 16px;
+ font-weight: 600;
+ padding: 14px;
+ border-bottom: 1px solid #f0f0f0;
+}
+.picker-body {
+ max-height: 60vh;
+}
+.picker-item {
+ padding: 14px;
+ border-bottom: 1px solid #f2f2f2;
+}
+.picker-item.active {
+ background: #f2f6ff;
+}
+.picker-item-text {
+ font-size: 14px;
+ color: #333;
+}
+.picker-actions {
+ padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
+ display: flex;
+ gap: 12px;
+ background: #fff;
+}
+
diff --git a/pages/case/new-followup.vue b/pages/case/new-followup.vue
index 76dcc9e..3995615 100644
--- a/pages/case/new-followup.vue
+++ b/pages/case/new-followup.vue
@@ -1,5 +1,5 @@
-
+
@@ -46,22 +46,6 @@
{{ (form.taskContent || '').length }}/200
-
-
- 跟进方式
-
- 待办
- 群发
- i
-
-
- 发送内容
-
-
- {{ (form.sendContent || '').length }}/500
-
-
-
-
-
- 跟进方式说明
-
- 待办:生成待办单,需员工手动进行处理
- 群发:生成群发单,员工可批量进行处理(wxapp mock 不接入群发)
+
+
+ 选择类型
+
+
+ {{ t.label }}
+
+
+
+
+
-
- 关闭
+
+
+
+
+
+ 选择处理人(本团队)
+
+
+ {{ String(m?.anotherName || m?.name || m?.userid || '') }}
+
+
+
+
+
@@ -88,42 +101,44 @@
import { computed, reactive, ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import dayjs from 'dayjs';
-import { ensureSeed, upsertFollowup } from '@/components/archive-detail/mock';
+import { storeToRefs } from 'pinia';
+import api from '@/utils/api';
+import useAccountStore from '@/store/account';
+import { toast } from '@/utils/widget';
+import { getTodoEventTypeLabel, getTodoEventTypeOptions } from '@/utils/todo-const';
const archiveId = ref('');
const archiveName = ref('');
+const customerData = ref({});
-const eventTypeList = [
- { label: '回访', value: 'followup' },
- { label: '复诊提醒', value: 'revisit' },
- { label: '问卷', value: 'questionnaire' },
- { label: '其他', value: 'other' },
-];
+const eventTypeList = getTodoEventTypeOptions();
-const teamOptions = [
- { label: '口腔一科(示例)', value: 'team_1' },
- { label: '正畸团队(示例)', value: 'team_2' },
-];
+const accountStore = useAccountStore();
+const { account, doctorInfo } = storeToRefs(accountStore);
+const { getDoctorInfo } = accountStore;
+
+const teamMembers = ref([]);
const form = reactive({
planExecutionTime: '',
- executeTeamId: '',
- executeTeamName: '',
- executorName: '',
+ executeTeamId: '', // teamId
+ executeTeamName: '', // teamName
+ executorUserId: '', // userid
+ executorName: '', // anotherName
eventType: '',
taskContent: '',
- executeMethod: 'todo', // todo | groupMessage
- sendContent: '',
});
-const eventTypeLabel = computed(() => eventTypeList.find((i) => i.value === form.eventType)?.label || '');
+const eventTypeLabel = computed(() => getTodoEventTypeLabel(form.eventType));
onLoad((options) => {
+ resetForm();
archiveId.value = options?.archiveId ? String(options.archiveId) : '';
const c = uni.getStorageSync('new-followup-customer');
if (c && typeof c === 'object') {
archiveId.value = archiveId.value || String(c._id || '');
archiveName.value = String(c.name || '');
+ customerData.value = c;
}
if (!archiveId.value) {
uni.showToast({ title: '缺少 archiveId', icon: 'none' });
@@ -131,19 +146,87 @@ onLoad((options) => {
return;
}
- ensureSeed(archiveId.value, { name: archiveName.value });
-
- // 使用模板:从 plan-list 选择后写入 select-mamagement-plan
- const plan = uni.getStorageSync('select-mamagement-plan');
- if (plan && typeof plan === 'object' && plan.planName) {
- form.eventType = plan.eventType || 'followup';
- form.taskContent = plan.taskContent || `执行回访计划:${plan.planName}`;
- uni.setNavigationBarTitle({ title: '使用模板新增任务' });
+ const cached = uni.getStorageSync('ykt_case_archive_detail');
+ if (cached && typeof cached === 'object' && String(cached?._id || '') === archiveId.value) {
+ customerData.value = cached;
+ archiveName.value = archiveName.value || String(cached.name || '');
}
if (!form.planExecutionTime) form.planExecutionTime = dayjs().add(1, 'day').format('YYYY-MM-DD');
+
+ initDefaultExecutor();
});
+function resetForm(keepCustomer = false) {
+ form.planExecutionTime = '';
+ form.executeTeamId = '';
+ form.executeTeamName = '';
+ form.executorUserId = '';
+ form.executorName = '';
+ form.eventType = '';
+ form.taskContent = '';
+ if (!keepCustomer) {
+ archiveId.value = '';
+ archiveName.value = '';
+ customerData.value = {};
+ }
+ teamMembers.value = [];
+ uni.setStorageSync('select-mamagement-plan', '');
+}
+
+async function ensureDoctor() {
+ if (doctorInfo.value) return;
+ if (!account.value?.openid) return;
+ try {
+ await getDoctorInfo();
+ } catch {
+ // ignore
+ }
+}
+
+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 loadTeamMembers(teamId) {
+ const corpId = getCorpId();
+ if (!corpId || !teamId) return;
+ const res = await api('getTeamData', { corpId, teamId });
+ if (!res?.success) {
+ teamMembers.value = [];
+ return;
+ }
+ const t = res?.data && typeof res.data === 'object' ? res.data : {};
+ teamMembers.value = Array.isArray(t.memberList) ? t.memberList : [];
+}
+
+async function initDefaultExecutor() {
+ await ensureDoctor();
+ const userId = getUserId();
+ const name = String(doctorInfo.value?.anotherName || doctorInfo.value?.name || '');
+ const currentTeam = uni.getStorageSync('ykt_case_current_team') || {};
+ const teamId = String(currentTeam.teamId || '');
+ const teamName = String(currentTeam.name || '');
+ if (teamId) {
+ form.executeTeamId = teamId;
+ form.executeTeamName = teamName;
+ await loadTeamMembers(teamId);
+ }
+ if (userId) {
+ form.executorUserId = userId;
+ form.executorName = name || '我';
+ }
+}
+
function changeDate(e) {
const date = String(e.detail.value || '');
if (dayjs().startOf('day').isAfter(dayjs(date))) {
@@ -154,25 +237,12 @@ function changeDate(e) {
}
function selectType() {
- uni.showActionSheet({
- itemList: eventTypeList.map((i) => i.label),
- success: ({ tapIndex }) => {
- form.eventType = eventTypeList[tapIndex]?.value || '';
- },
- });
+ typePopup.value?.open?.();
}
function selectExecutor() {
- // wxapp 先用 mock:选择团队 + 固定执行人
- uni.showActionSheet({
- itemList: teamOptions.map((i) => i.label),
- success: ({ tapIndex }) => {
- const t = teamOptions[tapIndex];
- form.executeTeamId = t.value;
- form.executeTeamName = t.label;
- form.executorName = '李医生';
- },
- });
+ if (!teamMembers.value.length) return toast('当前团队暂无可选成员');
+ executorPopup.value?.open?.();
}
function cancel() {
@@ -183,47 +253,76 @@ function cancel() {
});
}
-function save() {
+async function save() {
if (!form.planExecutionTime) return uni.showToast({ title: '请选择回访日期', icon: 'none' });
- if (!form.executorName) return uni.showToast({ title: '请选择处理人', icon: 'none' });
+ if (!form.executeTeamId) return uni.showToast({ title: '请选择处理人', icon: 'none' });
+ if (!form.executorUserId) return uni.showToast({ title: '请选择处理人', icon: 'none' });
if (!form.eventType) return uni.showToast({ title: '请选择类型', icon: 'none' });
if (!String(form.taskContent || '').trim()) return uni.showToast({ title: '请输入目的', icon: 'none' });
- if (form.executeMethod === 'groupMessage' && !String(form.sendContent || '').trim()) {
- return uni.showToast({ title: '请输入发送内容', icon: 'none' });
+
+ await ensureDoctor();
+ const corpId = getCorpId();
+ const userId = getUserId();
+ if (!corpId || !userId) {
+ toast('缺少用户/团队信息,请先完成登录与团队选择');
+ return;
}
+ const customer = customerData.value && typeof customerData.value === 'object' ? customerData.value : {};
+ const customerId = String(customer._id || archiveId.value || '');
+ const customerName = String(customer.name || archiveName.value || '');
+ const customerUserId = String(customer.externalUserId || customer.customerUserId || '') || '';
- const plannedExecutionTime = dayjs(form.planExecutionTime).valueOf();
- upsertFollowup({
- archiveId: archiveId.value,
- followup: {
- plannedExecutionTime,
- status: 'processing',
- eventStatusLabel: '待处理',
- eventType: form.eventType,
- eventTypeLabel: eventTypeLabel.value || '回访',
- executeTeamId: form.executeTeamId || 'team_1',
- executeTeamName: form.executeTeamName || '口腔一科(示例)',
- executorName: form.executorName,
- creatorName: '我',
- taskContent: form.taskContent,
- result: '',
- executeMethod: form.executeMethod,
- sendContent: form.executeMethod === 'groupMessage' ? form.sendContent : '',
- createTime: Date.now(),
- },
- });
+ const params = {
+ corpId,
+ customerId,
+ customerName,
+ customerUserId,
+ executeTeamId: form.executeTeamId,
+ executeTeamName: form.executeTeamName,
+ creatorUserId: userId,
+ userId: form.executorUserId || userId,
+ taskList: [
+ {
+ enableSend: false,
+ eventType: form.eventType,
+ executeMethod: 'todo',
+ executorUserId: form.executorUserId || userId,
+ planExecutionTime: form.planExecutionTime,
+ sendContent: '',
+ taskContent: form.taskContent,
+ taskId: `${Date.now()}_${Math.random().toString(16).slice(2)}`,
+ fileList: [],
+ },
+ ],
+ };
+ const res = await api('executeManagementPlanTodo', params);
+ if (!res?.success) {
+ toast(res?.message || '保存失败');
+ return;
+ }
uni.$emit('archive-detail:followup-changed');
- uni.showToast({ title: '保存成功', icon: 'success' });
+ toast('保存成功');
setTimeout(() => uni.navigateBack(), 300);
}
-const infoPopup = ref(null);
-function showInfo() {
- infoPopup.value?.open?.();
+const typePopup = ref(null);
+function pickType(v) {
+ form.eventType = String(v || '');
}
-function closeInfo() {
- infoPopup.value?.close?.();
+function closeTypePicker() {
+ typePopup.value?.close?.();
+}
+
+const executorPopup = ref(null);
+function pickExecutor(m) {
+ const id = String(m?.userid || '');
+ if (!id) return;
+ form.executorUserId = id;
+ form.executorName = String(m?.anotherName || m?.name || m?.userid || '') || '';
+}
+function closeExecutorPicker() {
+ executorPopup.value?.close?.();
}
@@ -291,6 +390,39 @@ function closeInfo() {
color: #666;
margin-bottom: 10px;
}
+.picker-sheet {
+ background: #fff;
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
+ overflow: hidden;
+}
+.picker-title {
+ text-align: center;
+ font-size: 16px;
+ font-weight: 600;
+ padding: 14px;
+ border-bottom: 1px solid #f0f0f0;
+}
+.picker-body {
+ max-height: 60vh;
+}
+.picker-item {
+ padding: 14px;
+ border-bottom: 1px solid #f2f2f2;
+}
+.picker-item.active {
+ background: #f2f6ff;
+}
+.picker-item-text {
+ font-size: 14px;
+ color: #333;
+}
+.picker-actions {
+ padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
+ display: flex;
+ gap: 12px;
+ background: #fff;
+}
.textarea-box {
border: 1px solid #e6e6e6;
border-radius: 8px;
@@ -405,4 +537,3 @@ function closeInfo() {
color: #fff;
}
-
diff --git a/pages/case/plan-execute.vue b/pages/case/plan-execute.vue
new file mode 100644
index 0000000..3a1792e
--- /dev/null
+++ b/pages/case/plan-execute.vue
@@ -0,0 +1,400 @@
+
+
+
+
+
+ 应用范围: {{ plan.planDetail || '无' }}
+
+
+
+
+
+
+ 开始时间
+ *
+
+
+ {{ form.planExecutionTime || '请选择开始时间' }}
+
+
+
+
+
+
+
+ 处理人
+ *
+
+
+ {{ form.executorName || '请选择处理人' }}
+
+
+
+
+
+
+ 暂无任务
+
+
+
+
+
+
+
+
+ 选择处理人(本团队)
+
+
+ {{ String(m?.anotherName || m?.name || m?.userid || '') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/case/plan-list.vue b/pages/case/plan-list.vue
index f4a9297..729243d 100644
--- a/pages/case/plan-list.vue
+++ b/pages/case/plan-list.vue
@@ -1,7 +1,8 @@
-
+
-
+ 加载中...
+
暂无回访计划
@@ -26,43 +27,117 @@
@@ -74,6 +149,11 @@ function preview(plan) {
.scroll {
height: 100vh;
}
+.loading {
+ padding: 16px;
+ font-size: 13px;
+ color: #9aa0a6;
+}
.item {
display: flex;
align-items: center;
@@ -147,4 +227,3 @@ function preview(plan) {
color: #9aa0a6;
}
-
diff --git a/pages/case/plan-preview.vue b/pages/case/plan-preview.vue
new file mode 100644
index 0000000..5d97676
--- /dev/null
+++ b/pages/case/plan-preview.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+ 应用范围: {{ plan.planDetail || '无' }}
+
+
+
+ 暂无任务
+
+
+
+
+
+
+
+
+
+
+
diff --git a/routes/index.js b/routes/index.js
index 7963e80..c55a31f 100644
--- a/routes/index.js
+++ b/routes/index.js
@@ -14,7 +14,8 @@ export default [
},
{
path: 'pages/message/index',
- meta: { title: '消息', login: false },
+ meta: { title: '聊天', login: false },
+ style: { enablePullDownRefresh: false },
},
{
path: 'pages/case/case',
@@ -84,6 +85,14 @@ export default [
path: 'pages/case/plan-list',
meta: { title: '回访计划', login: false },
},
+ {
+ path: 'pages/case/plan-preview',
+ meta: { title: '回访计划详情', login: false },
+ },
+ {
+ path: 'pages/case/plan-execute',
+ meta: { title: '执行回访计划', login: false },
+ },
{
path: 'pages/work/work',
meta: { title: '工作台', login: false }
@@ -97,4 +106,3 @@ export default [
meta: { title: '选择科室', login: false }
}
]
-
diff --git a/scripts/pre-build.js b/scripts/pre-build.js
index 0879e17..2d19353 100644
--- a/scripts/pre-build.js
+++ b/scripts/pre-build.js
@@ -1,6 +1,13 @@
const fs = require('fs');
const path = require('path');
+function stripJsonComments(content) {
+ // Remove /* */ comments first, then // comments.
+ return String(content || '')
+ .replace(/\/\*[\s\S]*?\*\//g, '')
+ .replace(/^\s*\/\/.*$/gm, '');
+}
+
// 读取 routes 配置并转换为 pages.json
function generatePagesJson() {
// 读取 routes/index.js
@@ -41,7 +48,7 @@ function generatePagesJson() {
if (fs.existsSync(pagesJsonPath)) {
const content = fs.readFileSync(pagesJsonPath, 'utf-8');
- pagesJson = JSON.parse(content);
+ pagesJson = JSON.parse(stripJsonComments(content));
}
// 转换 routes 为 pages 格式
diff --git a/utils/api.js b/utils/api.js
index 4fca60d..cb6a6ae 100644
--- a/utils/api.js
+++ b/utils/api.js
@@ -44,6 +44,19 @@ const urlsConfig = {
getUserSig: 'getUserSig',
sendSystemMessage: "sendSystemMessage",
getChatRecordsByGroupId: "getChatRecordsByGroupId"
+ },
+ todo: {
+ getCustomerTodos: 'getCustomerTodos',
+ getTodoById: 'getTodoById',
+ setTodoStatus: 'setTodoStatus',
+ updateTaskTodoResult: 'updateTaskTodoResult',
+ updateEvent: 'updateEvent',
+ removeTodo: 'removeTodo',
+ createEvents: 'createEvents',
+ executeManagementPlanTodo: 'executeManagementPlanTodo',
+ getManagementPlan: 'getManagementPlan',
+ getManagementPlanById: 'getManagementPlanById',
+ getNextFollowUpTime: 'getNextFollowUpTime',
}
}
diff --git a/utils/todo-const.js b/utils/todo-const.js
new file mode 100644
index 0000000..26a63b2
--- /dev/null
+++ b/utils/todo-const.js
@@ -0,0 +1,30 @@
+export const TODO_EVENT_TYPE_LABELS = {
+ followUpNoShow: '未到院回访',
+ followUpNoDeal: '未成交回访',
+ followUp: '诊后回访',
+ followUpPostSurgery: '术后回访',
+ followUpPostTreatment: '治疗后回访',
+ appointmentReminder: '就诊提醒',
+ followUpReminder: '复诊提醒',
+ medicationReminder: '用药提醒',
+ serviceSummary: '咨询服务',
+ eventNotification: '活动通知',
+ ContentReminder: '宣教发送',
+ questionnaire: '问卷调查',
+ followUpComplaint: '投诉回访',
+ followUpActivity: '活动回访',
+ other: '其他',
+};
+
+export function getTodoEventTypeLabel(value) {
+ const v = String(value || '');
+ return TODO_EVENT_TYPE_LABELS[v] || v || '其他';
+}
+
+export function getTodoEventTypeOptions() {
+ return Object.keys(TODO_EVENT_TYPE_LABELS).map((key) => ({
+ value: key,
+ label: TODO_EVENT_TYPE_LABELS[key],
+ }));
+}
+