ykt-wxapp/pages/case/followup-detail.vue

431 lines
11 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/followup-detail/followup-detail.vue简化移植去除微信会话/员工选择/接口 -->
<view class="card">
<view class="title-row">
<view class="title">{{ currentType.label }}</view>
<view class="tag" :class="`st-${currentStatus.value}`">{{ currentStatus.label }}</view>
</view>
<view class="desc">{{ form.taskContent || '暂无任务内容' }}</view>
<view class="info-row">
<view class="info">计划执行时间{{ planDate || '--' }}</view>
<view class="info">执行人{{ form.executorName || '--' }}</view>
</view>
</view>
<view class="card">
<view class="section-title">回访方式</view>
<view class="method-row" @click="toggleMethod('phone')">
<image class="radio" :src="`/static/circle${todoMethod === 'phone' ? 'd' : ''}.svg`" />
<view class="method-label">电话</view>
<view class="method-input" @click.stop="pickPhone">
<view class="method-value" :class="{ muted: !phone }">{{ phone || '选择号码' }}</view>
<uni-icons type="arrowright" size="16" color="#999" />
</view>
</view>
<view class="method-row" @click="toggleMethod('wechat')">
<image class="radio" :src="`/static/circle${todoMethod === 'wechat' ? 'd' : ''}.svg`" />
<view class="method-label">微信</view>
<view class="method-note">mock不接入会话</view>
</view>
</view>
<view class="card">
<view class="section-title">回访结果</view>
<view class="textarea-box">
<textarea v-model="form.result" class="textarea tall" placeholder="请填写回访结果" maxlength="500" />
<view class="counter">{{ (form.result || '').length }}/500</view>
</view>
</view>
<view class="footer">
<button class="btn plain" @click="cancel">返回</button>
<button v-if="canEdit" class="btn plain danger" @click="cancelTask">取消任务</button>
<button v-if="canEdit" class="btn primary" @click="markDone">设为完成</button>
<button v-else class="btn primary" @click="save">保存</button>
</view>
<view v-if="taskId" class="delete-fab" @click="remove">
<uni-icons type="trash" size="22" color="#ff4d4f" />
</view>
</view>
</template>
<script setup>
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';
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 statusOptions = [
{ label: '待处理', value: 'processing' },
{ label: '未开始', value: 'notStart' },
{ label: '已完成', value: 'treated' },
{ label: '已取消', value: 'cancelled' },
{ label: '已过期', value: 'expired' },
];
const currentStatus = ref(statusOptions[0]);
const teamOptions = [
{ label: '口腔一科(示例)', value: 'team_1' },
{ label: '正畸团队(示例)', value: 'team_2' },
];
const currentTeam = ref(teamOptions[0]);
const planDate = ref('');
const form = reactive({
executorName: '',
taskContent: '',
result: '',
});
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;
});
const canEdit = computed(() => ['processing', 'notStart'].includes(currentStatus.value.value));
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, {});
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;
}
}
planDate.value = dayjs().format('YYYY-MM-DD');
});
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 cancel() {
uni.navigateBack();
}
function save() {
if (!archiveId.value) {
uni.showToast({ title: '缺少 archiveId', icon: 'none' });
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);
}
function toggleMethod(v) {
if (!canEdit.value) return;
todoMethod.value = v;
if (v !== 'phone') phone.value = '';
}
function pickPhone() {
if (!canEdit.value) return;
uni.showActionSheet({
itemList: mobiles.value,
success: ({ tapIndex }) => {
phone.value = mobiles.value[tapIndex] || '';
todoMethod.value = 'phone';
},
});
}
function markDone() {
if (!canEdit.value) return;
if (!['phone', 'wechat'].includes(todoMethod.value)) return uni.showToast({ title: '请选择回访方式', icon: 'none' });
uni.showModal({
title: '提示',
content: '确定完成该回访任务吗?',
success: (res) => {
if (!res.confirm) return;
currentStatus.value = statusOptions.find((i) => i.value === 'treated') || currentStatus.value;
save();
},
});
}
function cancelTask() {
if (!canEdit.value) return;
uni.showModal({
title: '提示',
content: '确定取消该回访任务吗?',
success: (res) => {
if (!res.confirm) return;
currentStatus.value = statusOptions.find((i) => i.value === 'cancelled') || currentStatus.value;
save();
},
});
}
function remove() {
uni.showModal({
title: '提示',
content: '确定删除当前记录?',
success: (res) => {
if (!res.confirm) return;
removeFollowup({ archiveId: archiveId.value, id: taskId.value });
uni.$emit('archive-detail:followup-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));
}
.card {
background: #fff;
margin: 10px 14px 0;
border-radius: 8px;
padding: 14px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}
.title-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.tag {
font-size: 12px;
padding: 6px 10px;
border-radius: 6px;
background: #f3f4f6;
color: #666;
}
.st-processing {
background: #ffe5e5;
color: #ff4d4f;
}
.st-notStart {
background: #dbe6ff;
color: #4f6ef7;
}
.st-treated {
background: #dcfce7;
color: #16a34a;
}
.st-cancelled,
.st-expired {
background: #f3f4f6;
color: #666;
}
.desc {
margin-top: 10px;
font-size: 14px;
color: #666;
line-height: 20px;
}
.info-row {
margin-top: 10px;
display: flex;
justify-content: space-between;
gap: 10px;
}
.info {
font-size: 12px;
color: #999;
}
.section-title {
font-size: 15px;
font-weight: 600;
color: #333;
margin-bottom: 10px;
}
.method-row {
display: flex;
align-items: center;
padding: 12px 0;
}
.radio {
width: 16px;
height: 16px;
margin-right: 10px;
}
.method-label {
font-size: 14px;
color: #333;
margin-right: 10px;
}
.method-note {
font-size: 12px;
color: #999;
}
.method-input {
margin-left: auto;
width: 220px;
height: 36px;
border: 1px solid #e6e6e6;
border-radius: 6px;
padding: 0 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.method-value {
font-size: 13px;
color: #333;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.method-value.muted {
color: #999;
}
.textarea-box {
border: 1px solid #e6e6e6;
border-radius: 8px;
padding: 10px;
}
.textarea {
width: 100%;
height: 140px;
font-size: 14px;
box-sizing: border-box;
}
.textarea.tall {
height: 140px;
}
.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.plain.danger {
color: #ff4d4f;
border-color: #ff4d4f;
}
.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>