315 lines
9.4 KiB
Vue
315 lines
9.4 KiB
Vue
|
|
<template>
|
||
|
|
<view class="page">
|
||
|
|
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/service-record-detail/service-record-detail.vue -->
|
||
|
|
<view class="body">
|
||
|
|
<scroll-view scroll-y class="scroll">
|
||
|
|
<view class="card">
|
||
|
|
<view class="section-title">执行日期</view>
|
||
|
|
<picker mode="date" @change="pickDate">
|
||
|
|
<view class="picker-box">
|
||
|
|
<uni-icons type="calendar" size="18" color="#666" class="mr" />
|
||
|
|
<view class="picker-text" :class="{ muted: !date }">{{ date || '请选择服务日期' }}</view>
|
||
|
|
</view>
|
||
|
|
</picker>
|
||
|
|
|
||
|
|
<view class="section-title">执行时间</view>
|
||
|
|
<picker mode="time" @change="pickTime">
|
||
|
|
<view class="picker-box">
|
||
|
|
<uni-icons type="calendar" size="18" color="#666" class="mr" />
|
||
|
|
<view class="picker-text" :class="{ muted: !time }">{{ time || '请选择执行时间' }}</view>
|
||
|
|
</view>
|
||
|
|
</picker>
|
||
|
|
|
||
|
|
<view class="section-title">服务类型</view>
|
||
|
|
<picker mode="selector" :range="typeOptions" range-key="label" @change="pickType" :disabled="Boolean(recordId)">
|
||
|
|
<view class="picker-box between">
|
||
|
|
<view class="picker-text" :class="{ muted: !currentType.value }">
|
||
|
|
{{ currentType.value ? currentType.label : '请选择服务类型' }}
|
||
|
|
</view>
|
||
|
|
<uni-icons type="arrowright" size="16" color="#999" />
|
||
|
|
</view>
|
||
|
|
</picker>
|
||
|
|
|
||
|
|
<view class="section-title">所属团队</view>
|
||
|
|
<picker mode="selector" :range="teamOptions" range-key="label" @change="pickTeam" :disabled="Boolean(recordId)">
|
||
|
|
<view class="picker-box between">
|
||
|
|
<view class="picker-text" :class="{ muted: !currentTeam.value }">
|
||
|
|
{{ currentTeam.value ? currentTeam.label : '请选择所属团队' }}
|
||
|
|
</view>
|
||
|
|
<uni-icons type="arrowright" size="16" color="#999" />
|
||
|
|
</view>
|
||
|
|
</picker>
|
||
|
|
|
||
|
|
<view class="section-title">服务内容</view>
|
||
|
|
<view class="textarea-box">
|
||
|
|
<textarea v-model="form.taskContent" class="textarea tall" maxlength="1000" placeholder="请输入服务小结内容" />
|
||
|
|
<view class="counter">{{ (form.taskContent || '').length }}/1000</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 style="height: 120px;"></view>
|
||
|
|
</scroll-view>
|
||
|
|
|
||
|
|
<view class="footer">
|
||
|
|
<button class="btn plain" @click="cancel">取消</button>
|
||
|
|
<button class="btn primary" @click="save">保存</button>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
|
||
|
|
<view v-if="recordId" class="delete-fab" @click="remove">
|
||
|
|
<uni-icons type="trash" size="22" color="#ff4d4f" />
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup>
|
||
|
|
import { reactive, ref } from 'vue';
|
||
|
|
import { onLoad } from '@dcloudio/uni-app';
|
||
|
|
import dayjs from 'dayjs';
|
||
|
|
import { ensureSeed, getServiceRecord, removeServiceRecord, upsertServiceRecord } from '@/components/archive-detail/mock';
|
||
|
|
|
||
|
|
const archiveId = ref('');
|
||
|
|
const mode = ref('add');
|
||
|
|
const recordId = ref('');
|
||
|
|
|
||
|
|
const typeOptions = [
|
||
|
|
{ label: '电话回访', value: 'phone' },
|
||
|
|
{ label: '短信提醒', value: 'sms' },
|
||
|
|
{ label: '问卷', value: 'questionnaire' },
|
||
|
|
{ label: '文章', value: 'article' },
|
||
|
|
];
|
||
|
|
const currentType = ref(typeOptions[0]);
|
||
|
|
|
||
|
|
const teamOptions = [
|
||
|
|
{ label: '口腔一科(示例)', value: 'team_1' },
|
||
|
|
{ label: '正畸团队(示例)', value: 'team_2' },
|
||
|
|
];
|
||
|
|
const currentTeam = ref(teamOptions[0]);
|
||
|
|
|
||
|
|
const date = ref('');
|
||
|
|
const time = ref('');
|
||
|
|
const form = reactive({
|
||
|
|
executorName: '',
|
||
|
|
taskContent: '',
|
||
|
|
result: '',
|
||
|
|
articleUrl: '',
|
||
|
|
surveryId: '',
|
||
|
|
});
|
||
|
|
|
||
|
|
onLoad((options) => {
|
||
|
|
archiveId.value = options?.archiveId ? String(options.archiveId) : '';
|
||
|
|
recordId.value = options?.id ? String(options.id) : '';
|
||
|
|
|
||
|
|
ensureSeed(archiveId.value, {});
|
||
|
|
|
||
|
|
if (recordId.value) {
|
||
|
|
const record = getServiceRecord({ archiveId: archiveId.value, id: recordId.value });
|
||
|
|
if (record) {
|
||
|
|
currentType.value = typeOptions.find((i) => i.value === record.eventType) || typeOptions[0];
|
||
|
|
currentTeam.value = teamOptions.find((i) => i.value === record.executeTeamId) || teamOptions[0];
|
||
|
|
date.value = record.executionTime ? dayjs(record.executionTime).format('YYYY-MM-DD') : '';
|
||
|
|
time.value = record.executionTime ? dayjs(record.executionTime).format('HH:mm') : '';
|
||
|
|
form.executorName = record.executorName || '';
|
||
|
|
form.taskContent = record.taskContent || '';
|
||
|
|
form.result = record.result || '';
|
||
|
|
form.articleUrl = record.pannedEventSendFile?.type === 'article' ? record.pannedEventSendFile.url || '' : '';
|
||
|
|
form.surveryId = record.pannedEventSendFile?.type === 'questionnaire' ? record.pannedEventSendFile.surveryId || '' : '';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
date.value = dayjs().format('YYYY-MM-DD');
|
||
|
|
});
|
||
|
|
|
||
|
|
function pickType(e) {
|
||
|
|
currentType.value = typeOptions[e.detail.value] || typeOptions[0];
|
||
|
|
}
|
||
|
|
function pickTeam(e) {
|
||
|
|
currentTeam.value = teamOptions[e.detail.value] || teamOptions[0];
|
||
|
|
}
|
||
|
|
function pickDate(e) {
|
||
|
|
date.value = e.detail.value || '';
|
||
|
|
}
|
||
|
|
function pickTime(e) {
|
||
|
|
time.value = e.detail.value || '';
|
||
|
|
}
|
||
|
|
|
||
|
|
function cancel() {
|
||
|
|
uni.navigateBack();
|
||
|
|
}
|
||
|
|
|
||
|
|
function save() {
|
||
|
|
if (!archiveId.value) {
|
||
|
|
uni.showToast({ title: '缺少 archiveId', icon: 'none' });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!date.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 (!currentTeam.value?.value) 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 pannedEventSendFile =
|
||
|
|
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({
|
||
|
|
archiveId: archiveId.value,
|
||
|
|
record: {
|
||
|
|
_id: recordId.value || '',
|
||
|
|
eventType: currentType.value.value,
|
||
|
|
executionTime,
|
||
|
|
executeTeamId: currentTeam.value.value,
|
||
|
|
executeTeamName: currentTeam.value.label,
|
||
|
|
executorName: form.executorName,
|
||
|
|
taskContent: form.taskContent,
|
||
|
|
result: form.result,
|
||
|
|
pannedEventSendFile,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
uni.$emit('archive-detail:service-record-changed');
|
||
|
|
uni.showToast({ title: '保存成功', icon: 'success' });
|
||
|
|
setTimeout(() => uni.navigateBack(), 300);
|
||
|
|
}
|
||
|
|
|
||
|
|
function remove() {
|
||
|
|
uni.showModal({
|
||
|
|
title: '提示',
|
||
|
|
content: '确定删除当前记录?',
|
||
|
|
success: (res) => {
|
||
|
|
if (!res.confirm) return;
|
||
|
|
removeServiceRecord({ archiveId: archiveId.value, id: recordId.value });
|
||
|
|
uni.$emit('archive-detail:service-record-changed');
|
||
|
|
uni.showToast({ title: '已删除', icon: 'success' });
|
||
|
|
setTimeout(() => uni.navigateBack(), 300);
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.page {
|
||
|
|
min-height: 100vh;
|
||
|
|
background: #f5f6f8;
|
||
|
|
padding-bottom: calc(76px + env(safe-area-inset-bottom));
|
||
|
|
}
|
||
|
|
.body {
|
||
|
|
height: 100vh;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
}
|
||
|
|
.scroll {
|
||
|
|
flex: 1;
|
||
|
|
}
|
||
|
|
.card {
|
||
|
|
background: #fff;
|
||
|
|
padding: 14px;
|
||
|
|
margin: 10px 14px 0;
|
||
|
|
border-radius: 8px;
|
||
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
|
||
|
|
}
|
||
|
|
.section-title {
|
||
|
|
font-size: 16px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: #333;
|
||
|
|
margin: 12px 0 10px;
|
||
|
|
}
|
||
|
|
.section-title:first-child {
|
||
|
|
margin-top: 0;
|
||
|
|
}
|
||
|
|
.picker-box {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
padding: 12px 12px;
|
||
|
|
border: 1px solid #e6e6e6;
|
||
|
|
border-radius: 6px;
|
||
|
|
background: #fff;
|
||
|
|
}
|
||
|
|
.picker-box.between {
|
||
|
|
justify-content: space-between;
|
||
|
|
}
|
||
|
|
.mr {
|
||
|
|
margin-right: 10px;
|
||
|
|
}
|
||
|
|
.picker-text {
|
||
|
|
font-size: 14px;
|
||
|
|
color: #333;
|
||
|
|
}
|
||
|
|
.picker-text.muted {
|
||
|
|
color: #999;
|
||
|
|
}
|
||
|
|
.textarea-box {
|
||
|
|
border: 1px solid #e6e6e6;
|
||
|
|
border-radius: 8px;
|
||
|
|
padding: 10px;
|
||
|
|
}
|
||
|
|
.textarea {
|
||
|
|
width: 100%;
|
||
|
|
height: 90px;
|
||
|
|
font-size: 14px;
|
||
|
|
box-sizing: border-box;
|
||
|
|
}
|
||
|
|
.textarea.tall {
|
||
|
|
height: 120px;
|
||
|
|
}
|
||
|
|
.counter {
|
||
|
|
margin-top: 6px;
|
||
|
|
text-align: right;
|
||
|
|
font-size: 12px;
|
||
|
|
color: #999;
|
||
|
|
}
|
||
|
|
.footer {
|
||
|
|
position: fixed;
|
||
|
|
left: 0;
|
||
|
|
right: 0;
|
||
|
|
bottom: 0;
|
||
|
|
background: #fff;
|
||
|
|
padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
|
||
|
|
display: flex;
|
||
|
|
gap: 12px;
|
||
|
|
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.06);
|
||
|
|
}
|
||
|
|
.btn {
|
||
|
|
flex: 1;
|
||
|
|
height: 44px;
|
||
|
|
line-height: 44px;
|
||
|
|
border-radius: 6px;
|
||
|
|
font-size: 15px;
|
||
|
|
}
|
||
|
|
.btn::after {
|
||
|
|
border: none;
|
||
|
|
}
|
||
|
|
.btn.plain {
|
||
|
|
background: #fff;
|
||
|
|
color: #4f6ef7;
|
||
|
|
border: 1px solid #4f6ef7;
|
||
|
|
}
|
||
|
|
.btn.primary {
|
||
|
|
background: #4f6ef7;
|
||
|
|
color: #fff;
|
||
|
|
}
|
||
|
|
|
||
|
|
.delete-fab {
|
||
|
|
position: fixed;
|
||
|
|
right: 16px;
|
||
|
|
bottom: calc(96px + env(safe-area-inset-bottom));
|
||
|
|
width: 52px;
|
||
|
|
height: 52px;
|
||
|
|
border-radius: 26px;
|
||
|
|
background: #fff;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
box-shadow: 0 10px 18px rgba(0, 0, 0, 0.12);
|
||
|
|
z-index: 30;
|
||
|
|
}
|
||
|
|
</style>
|