465 lines
12 KiB
Vue
465 lines
12 KiB
Vue
<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" :style="{ height: scrollHeight + 'px' }">
|
||
<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 scrollHeight = ref(0);
|
||
|
||
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) => {
|
||
try {
|
||
const { windowHeight } = uni.getSystemInfoSync();
|
||
scrollHeight.value = windowHeight || 0;
|
||
} catch {
|
||
scrollHeight.value = 0;
|
||
}
|
||
|
||
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 {
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
background: #f5f6f8;
|
||
padding-bottom: 0;
|
||
}
|
||
.body {
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.scroll {
|
||
width: 100%;
|
||
}
|
||
.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>
|