ykt-wxapp/pages/case/service-record-detail.vue
2026-02-06 18:04:34 +08:00

455 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/service-record-detail/service-record-detail.vue -->
<view v-if="!recordId" class="body">
<scroll-view scroll-y class="scroll">
<view class="card">
<view class="section-title">执行日期</view>
<picker mode="date" @change="pickDate" :disabled="true">
<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" :disabled="Boolean(recordId)">
<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" :disabled="true">
<view class="picker-box">
<view class="picker-text" :class="{ muted: !currentTeam?.value }">
{{ currentTeam?.value ? currentTeam.label : '请选择所属团队' }}
</view>
</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>
<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>
<!-- 编辑仅支持修改服务内容底部弹层 -->
<uni-popup v-else ref="editPopupRef" type="bottom" :mask-click="true" @maskClick="closeEdit">
<view class="sheet">
<view class="sheet-header">
<view class="sheet-header-left" />
<view class="sheet-title">修改服务内容</view>
<view class="sheet-close" @click="closeEdit">
<uni-icons type="closeempty" size="18" color="#333" />
</view>
</view>
<view class="sheet-body">
<textarea
v-model="form.taskContent"
class="sheet-textarea"
placeholder="请输入服务内容"
:maxlength="1000"
/>
<view class="counter">{{ (form.taskContent || '').length }}/1000</view>
</view>
<view class="sheet-footer">
<button class="primary-btn" @click="saveEdit">保存</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { nextTick, reactive, ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import dayjs from 'dayjs';
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 mode = ref('add');
const recordId = ref('');
const typeOptions = getServiceTypeOptions({ excludeCustomerUpdate: true });
const currentType = ref(null);
const teamOptions = ref([]);
const currentTeam = ref(null);
const date = ref('');
const time = ref('');
const form = reactive({
taskContent: '',
});
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 || {};
const t = uni.getStorageSync('ykt_case_current_team') || {};
return String(d.userid || d.userId || d.corpUserId || a.userid || a.userId || t.userId || t.userid || t.corpUserId || '') || '';
}
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;
}
onLoad((options) => {
archiveId.value = options?.archiveId ? String(options.archiveId) : '';
recordId.value = options?.id ? String(options.id) : '';
mode.value = options?.mode ? String(options.mode) : 'add';
const seed = uni.getStorageSync('service-record-detail') || null;
customerId.value = String(seed?.customerId || archiveId.value || '') || '';
customerName.value = String(seed?.customerName || '') || '';
customerUserId.value = String(seed?.customerUserId || '') || '';
const executionTime = seed?.executionTime && dayjs(seed.executionTime).isValid() ? dayjs(seed.executionTime) : null;
date.value = executionTime ? executionTime.format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD');
time.value = executionTime ? executionTime.format('HH:mm') : '';
form.taskContent = String(seed?.taskContent || '') || '';
const seedType = String(seed?.eventType || '') || '';
currentType.value = typeOptions.find((i) => i.value === seedType) || null;
// 新建:团队固定为当前团队,不支持切换
const cur = getCurrentTeam();
currentTeam.value = cur;
teamOptions.value = cur ? [cur] : [];
if (recordId.value) {
uni.setNavigationBarTitle({ title: '修改服务内容' });
nextTick(() => editPopupRef.value?.open?.());
} else {
uni.setNavigationBarTitle({ title: '新建服务记录' });
}
});
function pickType(e) {
currentType.value = typeOptions[e.detail.value] || null;
}
function pickDate(e) {
date.value = e.detail.value || '';
}
function pickTime(e) {
time.value = e.detail.value || '';
}
function cancel() {
uni.navigateBack();
}
function save() {
if (!customerId.value) return toast('缺少 customerId');
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();
submit(executionTime);
}
const editPopupRef = ref(null);
function closeEdit() {
editPopupRef.value?.close?.();
setTimeout(() => uni.navigateBack(), 200);
}
function saveEdit() {
if (!String(form.taskContent || '').trim()) return uni.showToast({ title: '请输入服务内容', icon: 'none' });
submitEdit();
}
async function submitEdit() {
await ensureDoctor();
const corpId = getCorpId();
const userId = getUserId();
if (!corpId || !userId) {
toast('缺少用户/团队信息');
return;
}
if (!recordId.value) return;
const res = await api('updateServiceRecord', {
corpId,
id: recordId.value,
params: {
taskContent: form.taskContent,
updateUserId: userId,
},
});
if (!res?.success) {
toast(res?.message || '修改失败');
return;
}
uni.$emit('archive-detail:service-record-changed');
uni.showToast({ title: '保存成功', icon: 'success' });
setTimeout(() => uni.navigateBack(), 300);
}
async function submit(executionTime) {
await ensureDoctor();
const corpId = getCorpId();
const userId = getUserId();
if (!corpId || !userId) {
toast('缺少用户/团队信息');
return;
}
const params = {
taskContent: form.taskContent,
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.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: #0877F1;
border: 1px solid #0877F1;
}
.btn.primary {
background: #0877F1;
color: #fff;
}
.sheet {
background: #fff;
border-top-left-radius: 14px;
border-top-right-radius: 14px;
padding-bottom: calc(14px + env(safe-area-inset-bottom));
}
.sheet-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
border-bottom: 1px solid #f0f0f0;
}
.sheet-header-left {
width: 24px;
height: 24px;
}
.sheet-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.sheet-close {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.sheet-body {
padding: 14px 16px 0;
}
.sheet-textarea {
width: 100%;
height: 180px;
padding: 12px;
border: 1px solid #e6e6e6;
border-radius: 8px;
font-size: 14px;
box-sizing: border-box;
}
.sheet-footer {
padding: 12px 16px 0;
}
.primary-btn {
width: 100%;
height: 44px;
line-height: 44px;
border-radius: 6px;
background: #0877F1;
color: #fff;
font-size: 15px;
}
.primary-btn::after {
border: none;
}
</style>