feat:服务记录接口接入

This commit is contained in:
Jafeng 2026-01-26 16:47:35 +08:00
parent 70d0f5e496
commit 8e72ec6d24
4 changed files with 397 additions and 110 deletions

View File

@ -45,7 +45,7 @@
</view> </view>
<view class="body"> <view class="body">
<view class="content" :class="{ clamp: !expandMap[i._id] }"> <view class="content" :class="{ clamp: !expandMap[i._id] }">
{{ i.taskContent || '暂无内容' }}<text v-if="i.result">处理结果: {{ i.result }}</text> {{ i.taskContentDisplay || '暂无内容' }}
</view> </view>
<image class="pen" src="/static/icons/icon-pen.svg" @click.stop="edit(i)" /> <image class="pen" src="/static/icons/icon-pen.svg" @click.stop="edit(i)" />
</view> </view>
@ -85,7 +85,11 @@
<script setup> <script setup>
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'; import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { ensureSeed, queryServiceRecords } from './mock'; import { storeToRefs } from 'pinia';
import api from '@/utils/api';
import useAccountStore from '@/store/account';
import { toast } from '@/utils/widget';
import { getServiceTypeLabel, getServiceTypeOptions } from '@/utils/service-type-const';
const props = defineProps({ const props = defineProps({
data: { type: Object, default: () => ({}) }, data: { type: Object, default: () => ({}) },
@ -94,21 +98,11 @@ const props = defineProps({
floatingBottom: { type: Number, default: 16 }, floatingBottom: { type: Number, default: 16 },
}); });
const typeList = [ const typeList = [{ label: '全部', value: 'ALL' }, ...getServiceTypeOptions({ excludeCustomerUpdate: true })];
{ label: '全部', value: 'ALL' }, const teamList = ref([{ label: '全部', value: 'ALL' }]);
{ label: '电话回访', value: 'phone' },
{ label: '短信提醒', value: 'sms' },
{ label: '问卷', value: 'questionnaire' },
{ label: '文章', value: 'article' },
];
const teamList = [
{ label: '全部', value: 'ALL' },
{ label: '口腔一科(示例)', value: 'team_1' },
{ label: '正畸团队(示例)', value: 'team_2' },
];
const currentType = ref(typeList[0]); const currentType = ref(typeList[0]);
const currentTeam = ref(teamList[0]); const currentTeam = ref(teamList.value[0]);
const dateRange = ref([]); const dateRange = ref([]);
const page = ref(1); const page = ref(1);
@ -117,6 +111,70 @@ const pages = ref(1);
const loading = ref(false); const loading = ref(false);
const list = ref([]); const list = ref([]);
const expandMap = ref({}); const expandMap = ref({});
const userNameMap = ref({});
const accountStore = useAccountStore();
const { account, doctorInfo } = storeToRefs(accountStore);
const { getDoctorInfo } = accountStore;
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 getCurrentTeamId() {
const t = uni.getStorageSync('ykt_case_current_team') || {};
return String(t.teamId || '') || '';
}
function resolveUserName(userId) {
const id = String(userId || '');
if (!id) return '';
const map = userNameMap.value || {};
return String(map[id] || '') || id;
}
function formatTaskContent(text) {
if (typeof text !== 'string') return '';
return text.replace(/&&&([^&]+)&&&/g, (_, id) => resolveUserName(id)).trim();
}
async function loadTeamMembers(teamId) {
const tid = String(teamId || '') || '';
if (!tid) return;
await ensureDoctor();
const corpId = getCorpId();
if (!corpId) return;
const res = await api('getTeamData', { corpId, teamId: tid });
if (!res?.success) return;
const t = res?.data && typeof res.data === 'object' ? res.data : {};
const members = Array.isArray(t.memberList) ? t.memberList : [];
const map = members.reduce((acc, m) => {
const uid = String(m?.userid || '');
if (!uid) return acc;
acc[uid] = String(m?.anotherName || m?.name || m?.userid || '') || uid;
return acc;
}, {});
userNameMap.value = { ...(userNameMap.value || {}), ...map };
}
const moreStatus = computed(() => { const moreStatus = computed(() => {
if (loading.value) return 'loading'; if (loading.value) return 'loading';
@ -133,10 +191,12 @@ function mapRow(i) {
const fileType = i.pannedEventSendFile?.type === 'article' ? 'article' : i.pannedEventSendFile?.type === 'questionnaire' ? 'questionnaire' : ''; const fileType = i.pannedEventSendFile?.type === 'article' ? 'article' : i.pannedEventSendFile?.type === 'questionnaire' ? 'questionnaire' : '';
return { return {
...i, ...i,
_id: String(i?._id || i?.id || ''),
hasFile, hasFile,
fileType, fileType,
timeStr: i.executionTime ? dayjs(i.executionTime).format('YYYY-MM-DD HH:mm') : '--', timeStr: i.executionTime ? dayjs(i.executionTime).format('YYYY-MM-DD HH:mm') : '--',
typeStr: typeList.find((t) => t.value === i.eventType)?.label || '', typeStr: getServiceTypeLabel(i.eventType),
taskContentDisplay: formatTaskContent(String(i?.taskContent || '')),
}; };
} }
@ -147,23 +207,62 @@ function reset() {
getMore(); getMore();
} }
function getMore() { function getEventTypeList() {
const v = currentType.value?.value || 'ALL';
if (v === 'ALL') return typeList.filter((i) => i.value !== 'ALL').map((i) => i.value);
return [v];
}
async function getMore() {
if (!props.archiveId) return; if (!props.archiveId) return;
if (loading.value) return; if (loading.value) return;
if (page.value > pages.value) return; if (page.value > pages.value) return;
loading.value = true; loading.value = true;
try { try {
const { list: arr, pages: p } = queryServiceRecords({ await ensureDoctor();
archiveId: props.archiveId, const corpId = getCorpId();
if (!corpId) {
toast('缺少 corpId请先完成登录/团队选择');
return;
}
const params = {
corpId,
customerId: String(props.archiveId),
eventTypeList: getEventTypeList(),
};
if (dateRange.value.length) params.executionTime = dateRange.value;
const teamId = currentTeam.value?.value && currentTeam.value.value !== 'ALL' ? currentTeam.value.value : '';
const res = await api('getServiceRecord', {
page: page.value, page: page.value,
pageSize, pageSize,
eventType: currentType.value.value, params,
teamId: currentTeam.value.value, queryType: '',
dateRange: dateRange.value, teamId,
}); });
if (!res?.success) {
toast(res?.message || '获取服务记录失败');
return;
}
const payload = res?.data;
const arr = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload) ? payload : [];
const p =
typeof payload?.pages === 'number'
? payload.pages
: typeof payload?.total === 'number'
? Math.ceil(payload.total / pageSize) || 0
: typeof res?.total === 'number'
? Math.ceil(res.total / pageSize) || 0
: arr.length < pageSize
? page.value
: page.value + 1;
pages.value = p; pages.value = p;
const mapped = arr.map(mapRow); const mapped = arr.map(mapRow).filter((i) => i && i._id);
list.value = page.value === 1 ? mapped : [...list.value, ...mapped]; list.value = page.value === 1 ? mapped : [...list.value, ...mapped];
page.value += 1; page.value += 1;
} finally { } finally {
@ -180,7 +279,9 @@ function pickType(e) {
reset(); reset();
} }
function pickTeam(e) { function pickTeam(e) {
currentTeam.value = teamList[e.detail.value] || teamList[0]; currentTeam.value = teamList.value[e.detail.value] || teamList.value[0];
const tid = currentTeam.value?.value && currentTeam.value.value !== 'ALL' ? currentTeam.value.value : getCurrentTeamId();
loadTeamMembers(tid);
reset(); reset();
} }
@ -194,10 +295,32 @@ function clearDates() {
} }
function add() { function add() {
const archive = props.data || {};
const customerUserId = String(archive.externalUserId || archive.customerUserId || '') || '';
uni.setStorageSync('service-record-detail', {
customerId: String(props.archiveId),
customerName: String(archive.name || ''),
customerUserId,
eventType: '',
});
uni.navigateTo({ url: `/pages/case/service-record-detail?archiveId=${encodeURIComponent(props.archiveId)}&mode=add` }); uni.navigateTo({ url: `/pages/case/service-record-detail?archiveId=${encodeURIComponent(props.archiveId)}&mode=add` });
} }
function edit(record) { function edit(record) {
const archive = props.data || {};
const customerUserId = String(archive.externalUserId || archive.customerUserId || '') || '';
uni.setStorageSync('service-record-detail', {
customerId: String(props.archiveId),
customerName: String(archive.name || ''),
customerUserId,
id: String(record?._id || ''),
executionTime: record?.executionTime || 0,
executeTeamId: String(record?.executeTeamId || ''),
executeTeamName: String(record?.executeTeamName || ''),
eventType: String(record?.eventType || ''),
taskContent: String(record?.taskContent || ''),
pannedEventSendFile: record?.pannedEventSendFile || null,
});
uni.navigateTo({ url: `/pages/case/service-record-detail?archiveId=${encodeURIComponent(props.archiveId)}&mode=edit&id=${encodeURIComponent(record._id)}` }); uni.navigateTo({ url: `/pages/case/service-record-detail?archiveId=${encodeURIComponent(props.archiveId)}&mode=edit&id=${encodeURIComponent(record._id)}` });
} }
@ -234,8 +357,30 @@ function copyFile() {
closeFilePopup(); closeFilePopup();
} }
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 : [];
const normalized = list
.map((raw) => {
if (!raw || typeof raw !== 'object') return null;
const teamId = raw.teamId || raw.id || raw._id || '';
const name = raw.name || raw.teamName || raw.team || '';
if (!teamId || !name) return null;
return { label: String(name), value: String(teamId) };
})
.filter(Boolean);
teamList.value = [{ label: '全部', value: 'ALL' }, ...normalized];
currentTeam.value = teamList.value.find((i) => i.value === currentTeam.value?.value) || teamList.value[0];
}
onMounted(() => { onMounted(() => {
ensureSeed(props.archiveId, props.data); loadTeams();
loadTeamMembers(getCurrentTeamId());
reset(); reset();
uni.$on('archive-detail:service-record-changed', reset); uni.$on('archive-detail:service-record-changed', reset);
}); });
@ -252,40 +397,55 @@ watch(
<style scoped> <style scoped>
.wrap { .wrap {
padding: 12px 0 96px; padding: 8px 0 96px;
} }
.filters { .filters {
padding: 10px 14px; padding: 6px 14px;
background: #f5f6f8; background: #f5f6f8;
border-bottom: 1px solid #f2f2f2; border-bottom: 1px solid #f2f2f2;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; flex-wrap: nowrap;
flex-wrap: wrap; }
.filters :deep(picker) {
display: block;
flex: 0 0 auto;
width: 100px;
}
.filters :deep(uni-datetime-picker) {
display: block;
flex: 1;
min-width: 0;
margin: 0 12px;
} }
.filter-pill { .filter-pill {
background: #fff; background: #fff;
border: 1px solid #e6e6e6; border: 1px solid #e6e6e6;
border-radius: 6px; border-radius: 8px;
padding: 10px 10px; padding: 8px 10px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 10px; min-width: 0;
min-width: 110px; width: 100%;
} }
.filter-pill.wide { .filter-pill.wide {
min-width: 160px; min-width: 0;
} }
.pill-text { .pill-text {
font-size: 13px; font-size: 13px;
color: #333; color: #333;
max-width: 190px; flex: 1;
min-width: 0;
margin-right: 8px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.filter-pill :deep(uni-icons) {
flex-shrink: 0;
}
.pill-text.muted { .pill-text.muted {
color: #999; color: #999;
} }
@ -295,8 +455,8 @@ watch(
.timeline { .timeline {
background: #fff; background: #fff;
margin-top: 10px; margin-top: 6px;
padding: 12px 0 70px; padding: 10px 0 70px;
} }
.cell { .cell {
padding: 0 14px; padding: 0 14px;

View File

@ -5,7 +5,7 @@
<scroll-view scroll-y class="scroll"> <scroll-view scroll-y class="scroll">
<view class="card"> <view class="card">
<view class="section-title">执行日期</view> <view class="section-title">执行日期</view>
<picker mode="date" @change="pickDate"> <picker mode="date" @change="pickDate" :disabled="true">
<view class="picker-box"> <view class="picker-box">
<uni-icons type="calendar" size="18" color="#666" class="mr" /> <uni-icons type="calendar" size="18" color="#666" class="mr" />
<view class="picker-text" :class="{ muted: !date }">{{ date || '请选择服务日期' }}</view> <view class="picker-text" :class="{ muted: !date }">{{ date || '请选择服务日期' }}</view>
@ -13,7 +13,7 @@
</picker> </picker>
<view class="section-title">执行时间</view> <view class="section-title">执行时间</view>
<picker mode="time" @change="pickTime"> <picker mode="time" @change="pickTime" :disabled="Boolean(recordId)">
<view class="picker-box"> <view class="picker-box">
<uni-icons type="calendar" size="18" color="#666" class="mr" /> <uni-icons type="calendar" size="18" color="#666" class="mr" />
<view class="picker-text" :class="{ muted: !time }">{{ time || '请选择执行时间' }}</view> <view class="picker-text" :class="{ muted: !time }">{{ time || '请选择执行时间' }}</view>
@ -23,8 +23,8 @@
<view class="section-title">服务类型</view> <view class="section-title">服务类型</view>
<picker mode="selector" :range="typeOptions" range-key="label" @change="pickType" :disabled="Boolean(recordId)"> <picker mode="selector" :range="typeOptions" range-key="label" @change="pickType" :disabled="Boolean(recordId)">
<view class="picker-box between"> <view class="picker-box between">
<view class="picker-text" :class="{ muted: !currentType.value }"> <view class="picker-text" :class="{ muted: !currentType?.value }">
{{ currentType.value ? currentType.label : '请选择服务类型' }} {{ currentType?.value ? currentType.label : '请选择服务类型' }}
</view> </view>
<uni-icons type="arrowright" size="16" color="#999" /> <uni-icons type="arrowright" size="16" color="#999" />
</view> </view>
@ -33,8 +33,8 @@
<view class="section-title">所属团队</view> <view class="section-title">所属团队</view>
<picker mode="selector" :range="teamOptions" range-key="label" @change="pickTeam" :disabled="Boolean(recordId)"> <picker mode="selector" :range="teamOptions" range-key="label" @change="pickTeam" :disabled="Boolean(recordId)">
<view class="picker-box between"> <view class="picker-box between">
<view class="picker-text" :class="{ muted: !currentTeam.value }"> <view class="picker-text" :class="{ muted: !currentTeam?.value }">
{{ currentTeam.value ? currentTeam.label : '请选择所属团队' }} {{ currentTeam?.value ? currentTeam.label : '请选择所属团队' }}
</view> </view>
<uni-icons type="arrowright" size="16" color="#999" /> <uni-icons type="arrowright" size="16" color="#999" />
</view> </view>
@ -45,12 +45,6 @@
<textarea v-model="form.taskContent" class="textarea tall" maxlength="1000" placeholder="请输入服务小结内容" /> <textarea v-model="form.taskContent" class="textarea tall" maxlength="1000" placeholder="请输入服务小结内容" />
<view class="counter">{{ (form.taskContent || '').length }}/1000</view> <view class="counter">{{ (form.taskContent || '').length }}/1000</view>
</view> </view>
<view class="section-title">处理结果</view>
<view class="textarea-box">
<textarea v-model="form.result" class="textarea" maxlength="500" placeholder="请输入处理结果(可选)" />
<view class="counter">{{ (form.result || '').length }}/500</view>
</view>
</view> </view>
<view style="height: 120px;"></view> <view style="height: 120px;"></view>
</scroll-view> </scroll-view>
@ -71,65 +65,122 @@
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app'; import { onLoad } from '@dcloudio/uni-app';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { ensureSeed, getServiceRecord, removeServiceRecord, upsertServiceRecord } 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 { getServiceTypeOptions } from '@/utils/service-type-const';
const archiveId = ref(''); const archiveId = ref('');
const mode = ref('add'); const mode = ref('add');
const recordId = ref(''); const recordId = ref('');
const typeOptions = [ const typeOptions = getServiceTypeOptions({ excludeCustomerUpdate: true });
{ label: '电话回访', value: 'phone' }, const currentType = ref(null);
{ label: '短信提醒', value: 'sms' },
{ label: '问卷', value: 'questionnaire' },
{ label: '文章', value: 'article' },
];
const currentType = ref(typeOptions[0]);
const teamOptions = [ const teamOptions = ref([]);
{ label: '口腔一科(示例)', value: 'team_1' }, const currentTeam = ref(null);
{ label: '正畸团队(示例)', value: 'team_2' },
];
const currentTeam = ref(teamOptions[0]);
const date = ref(''); const date = ref('');
const time = ref(''); const time = ref('');
const form = reactive({ const form = reactive({
executorName: '',
taskContent: '', taskContent: '',
result: '',
articleUrl: '',
surveryId: '',
}); });
const customerId = ref('');
const customerName = ref('');
const customerUserId = ref('');
const accountStore = useAccountStore();
const { account, doctorInfo } = storeToRefs(accountStore);
const { getDoctorInfo } = accountStore;
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 getCurrentTeam() {
const t = uni.getStorageSync('ykt_case_current_team') || {};
const teamId = String(t.teamId || '') || '';
const name = String(t.name || t.teamName || '') || '';
return teamId ? { value: teamId, label: name || teamId } : null;
}
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 : [];
teamOptions.value = list
.map((raw) => {
if (!raw || typeof raw !== 'object') return null;
const teamId = raw.teamId || raw.id || raw._id || '';
const name = raw.name || raw.teamName || raw.team || '';
if (!teamId) return null;
return { label: String(name || teamId), value: String(teamId) };
})
.filter(Boolean);
}
onLoad((options) => { onLoad((options) => {
archiveId.value = options?.archiveId ? String(options.archiveId) : ''; archiveId.value = options?.archiveId ? String(options.archiveId) : '';
recordId.value = options?.id ? String(options.id) : ''; recordId.value = options?.id ? String(options.id) : '';
ensureSeed(archiveId.value, {}); mode.value = options?.mode ? String(options.mode) : 'add';
const seed = uni.getStorageSync('service-record-detail') || null;
if (recordId.value) { customerId.value = String(seed?.customerId || archiveId.value || '') || '';
const record = getServiceRecord({ archiveId: archiveId.value, id: recordId.value }); customerName.value = String(seed?.customerName || '') || '';
if (record) { customerUserId.value = String(seed?.customerUserId || '') || '';
currentType.value = typeOptions.find((i) => i.value === record.eventType) || typeOptions[0];
currentTeam.value = teamOptions.find((i) => i.value === record.executeTeamId) || teamOptions[0]; const executionTime = seed?.executionTime && dayjs(seed.executionTime).isValid() ? dayjs(seed.executionTime) : null;
date.value = record.executionTime ? dayjs(record.executionTime).format('YYYY-MM-DD') : ''; date.value = executionTime ? executionTime.format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD');
time.value = record.executionTime ? dayjs(record.executionTime).format('HH:mm') : ''; time.value = executionTime ? executionTime.format('HH:mm') : '';
form.executorName = record.executorName || ''; form.taskContent = String(seed?.taskContent || '') || '';
form.taskContent = record.taskContent || '';
form.result = record.result || ''; const seedType = String(seed?.eventType || '') || '';
form.articleUrl = record.pannedEventSendFile?.type === 'article' ? record.pannedEventSendFile.url || '' : ''; currentType.value = typeOptions.find((i) => i.value === seedType) || null;
form.surveryId = record.pannedEventSendFile?.type === 'questionnaire' ? record.pannedEventSendFile.surveryId || '' : '';
return; const seedTeamId = String(seed?.executeTeamId || '') || '';
const seedTeamName = String(seed?.executeTeamName || '') || '';
if (seedTeamId) currentTeam.value = { value: seedTeamId, label: seedTeamName || seedTeamId };
else currentTeam.value = getCurrentTeam();
loadTeams().then(() => {
if (seedTeamId) currentTeam.value = teamOptions.value.find((t) => t.value === seedTeamId) || currentTeam.value;
else {
const cur = getCurrentTeam();
if (cur) currentTeam.value = teamOptions.value.find((t) => t.value === cur.value) || cur;
} }
} });
date.value = dayjs().format('YYYY-MM-DD');
}); });
function pickType(e) { function pickType(e) {
currentType.value = typeOptions[e.detail.value] || typeOptions[0]; currentType.value = typeOptions[e.detail.value] || null;
} }
function pickTeam(e) { function pickTeam(e) {
currentTeam.value = teamOptions[e.detail.value] || teamOptions[0]; currentTeam.value = teamOptions.value[e.detail.value] || null;
} }
function pickDate(e) { function pickDate(e) {
date.value = e.detail.value || ''; date.value = e.detail.value || '';
@ -143,10 +194,7 @@ function cancel() {
} }
function save() { function save() {
if (!archiveId.value) { if (!customerId.value) return toast('缺少 customerId');
uni.showToast({ title: '缺少 archiveId', icon: 'none' });
return;
}
if (!date.value) return uni.showToast({ title: '请选择服务日期', icon: 'none' }); if (!date.value) return uni.showToast({ title: '请选择服务日期', icon: 'none' });
if (!time.value) return uni.showToast({ title: '请选择执行时间', icon: 'none' }); if (!time.value) return uni.showToast({ title: '请选择执行时间', icon: 'none' });
if (!currentType.value?.value) return uni.showToast({ title: '请选择服务类型', icon: 'none' }); if (!currentType.value?.value) return uni.showToast({ title: '请选择服务类型', icon: 'none' });
@ -154,27 +202,52 @@ function save() {
if (!String(form.taskContent || '').trim()) return uni.showToast({ title: '请输入服务内容', icon: 'none' }); if (!String(form.taskContent || '').trim()) return uni.showToast({ title: '请输入服务内容', icon: 'none' });
const executionTime = dayjs(`${date.value} ${time.value}`).isValid() ? dayjs(`${date.value} ${time.value}`).valueOf() : Date.now(); const executionTime = dayjs(`${date.value} ${time.value}`).isValid() ? dayjs(`${date.value} ${time.value}`).valueOf() : Date.now();
const pannedEventSendFile = submit(executionTime);
currentType.value.value === 'article' }
? (form.articleUrl ? { type: 'article', url: form.articleUrl } : null)
: currentType.value.value === 'questionnaire'
? (form.surveryId ? { type: 'questionnaire', surveryId: form.surveryId } : null)
: null;
upsertServiceRecord({ async function submit(executionTime) {
archiveId: archiveId.value, await ensureDoctor();
record: { const corpId = getCorpId();
_id: recordId.value || '', const userId = getUserId();
eventType: currentType.value.value, if (!corpId || !userId) {
executionTime, toast('缺少用户/团队信息');
executeTeamId: currentTeam.value.value, return;
executeTeamName: currentTeam.value.label, }
executorName: form.executorName,
taskContent: form.taskContent, const params = {
result: form.result, taskContent: form.taskContent,
pannedEventSendFile, executionTime,
}, eventType: currentType.value.value,
}); executeTeamId: currentTeam.value.value,
executeTeamName: currentTeam.value.label,
};
let res;
if (recordId.value) {
res = await api('updateServiceRecord', {
corpId,
id: recordId.value,
params: {
...params,
updateUserId: userId,
},
});
} else {
res = await api('addServiceRecord', {
...params,
corpId,
creatorUserId: userId,
executorUserId: userId,
customerId: customerId.value,
customerName: customerName.value,
customerUserId: customerUserId.value,
});
}
if (!res?.success) {
toast(res?.message || (recordId.value ? '修改失败' : '新增失败'));
return;
}
uni.$emit('archive-detail:service-record-changed'); uni.$emit('archive-detail:service-record-changed');
uni.showToast({ title: '保存成功', icon: 'success' }); uni.showToast({ title: '保存成功', icon: 'success' });
setTimeout(() => uni.navigateBack(), 300); setTimeout(() => uni.navigateBack(), 300);
@ -184,9 +257,16 @@ function remove() {
uni.showModal({ uni.showModal({
title: '提示', title: '提示',
content: '确定删除当前记录?', content: '确定删除当前记录?',
success: (res) => { success: async (res) => {
if (!res.confirm) return; if (!res.confirm) return;
removeServiceRecord({ archiveId: archiveId.value, id: recordId.value }); await ensureDoctor();
const corpId = getCorpId();
if (!corpId || !recordId.value) return;
const resp = await api('removeServiceRecord', { corpId, id: recordId.value });
if (!resp?.success) {
toast(resp?.message || '删除失败');
return;
}
uni.$emit('archive-detail:service-record-changed'); uni.$emit('archive-detail:service-record-changed');
uni.showToast({ title: '已删除', icon: 'success' }); uni.showToast({ title: '已删除', icon: 'success' });
setTimeout(() => uni.navigateBack(), 300); setTimeout(() => uni.navigateBack(), 300);

View File

@ -57,6 +57,11 @@ const urlsConfig = {
getManagementPlan: 'getManagementPlan', getManagementPlan: 'getManagementPlan',
getManagementPlanById: 'getManagementPlanById', getManagementPlanById: 'getManagementPlanById',
getNextFollowUpTime: 'getNextFollowUpTime', getNextFollowUpTime: 'getNextFollowUpTime',
// 服务记录(对齐 ykt-management-mobile/src/api/todo.js
getServiceRecord: 'getServiceRecord',
addServiceRecord: 'addServiceRecord',
updateServiceRecord: 'updateServiceRecord',
removeServiceRecord: 'removeServiceRecord',
} }
} }

View File

@ -0,0 +1,42 @@
export const SERVICE_TYPE_LABELS = {
remindFiling: '新建档案',
addVisitRecord: '预约登记',
ContentReminder: '宣教发送',
questionnaire: '问卷调查',
customerUpdate: '更新客户档案',
serviceSummary: '咨询服务',
visitRegistration: '就诊登记',
followUpNoShow: '未到院回访',
followUpNoDeal: '未成交回访',
followUp: '诊后回访',
followUpPostSurgery: '术后回访',
followUpPostTreatment: '治疗后回访',
appointmentReminder: '就诊提醒',
followUpReminder: '复诊提醒',
medicationReminder: '用药提醒',
eventNotification: '活动通知',
transferToSameTeam: '客户转移(团队内转)',
transferToOtherTeam: '客户转移(转别的团队)',
transferToCustomerPool: '客户转移(转公共客户池)',
adminAllocateTeams: '管理员分配团队',
adminRemoveTeams: '管理员解除团队',
share: '客户共享',
followUpComplaint: '投诉回访',
followUpActivity: '活动回访',
other: '其他',
Feedback: '意见反馈',
treatmentAppointment: '治疗预约',
followupAppointment: '复诊预约',
confirmArrival: '确认到院',
};
export function getServiceTypeLabel(value) {
const v = String(value || '');
return SERVICE_TYPE_LABELS[v] || v || '其他';
}
export function getServiceTypeOptions({ excludeCustomerUpdate = true } = {}) {
return Object.keys(SERVICE_TYPE_LABELS)
.filter((key) => (excludeCustomerUpdate ? key !== 'customerUpdate' : true))
.map((key) => ({ value: key, label: SERVICE_TYPE_LABELS[key] }));
}