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

409 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>
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/new-followup/new-followup.vue简化移植去除员工组件/群发附件/接口 -->
<view class="page">
<view class="card">
<picker mode="date" :value="form.planExecutionTime" @change="changeDate">
<view class="row clickable">
<view class="left">
<view class="label">回访日期</view>
<view class="req">*</view>
</view>
<view class="right">
<view class="value" :class="{ muted: !form.planExecutionTime }">{{ form.planExecutionTime || '请选择回访日期' }}</view>
<uni-icons type="arrowright" size="16" color="#999" />
</view>
</view>
</picker>
<view class="row clickable" @click="selectExecutor">
<view class="left">
<view class="label">处理人</view>
<view class="req">*</view>
</view>
<view class="right">
<view class="value" :class="{ muted: !form.executorName }">
{{ form.executorName ? `${form.executorName}${form.executeTeamName ? `(${form.executeTeamName})` : ''}` : '请选择处理人' }}
</view>
<uni-icons type="arrowright" size="16" color="#999" />
</view>
</view>
<view class="row clickable" @click="selectType">
<view class="left">
<view class="label">类型</view>
<view class="req">*</view>
</view>
<view class="right">
<view class="value" :class="{ muted: !form.eventType }">{{ eventTypeLabel || '请选择类型' }}</view>
<uni-icons type="arrowright" size="16" color="#999" />
</view>
</view>
<view class="block">
<view class="block-title">目的</view>
<view class="textarea-box">
<textarea v-model="form.taskContent" class="textarea" placeholder="请输入文字提醒" maxlength="200" />
<view class="counter">{{ (form.taskContent || '').length }}/200</view>
</view>
</view>
<view class="block">
<view class="block-title">跟进方式</view>
<view class="toggle-row">
<view class="pill" :class="{ active: form.executeMethod === 'todo' }" @click="form.executeMethod = 'todo'">待办</view>
<view class="pill" :class="{ active: form.executeMethod === 'groupMessage' }" @click="form.executeMethod = 'groupMessage'">群发</view>
<view class="info" @click="showInfo">i</view>
</view>
<view v-if="form.executeMethod === 'groupMessage'" class="block">
<view class="block-title">发送内容</view>
<view class="textarea-box">
<textarea v-model="form.sendContent" class="textarea" placeholder="请输入群发内容mock" maxlength="500" />
<view class="counter">{{ (form.sendContent || '').length }}/500</view>
</view>
</view>
</view>
</view>
<view class="footer">
<button class="btn plain" @click="cancel">取消</button>
<button class="btn primary" @click="save">保存</button>
</view>
<uni-popup ref="infoPopup" type="center">
<view class="modal">
<view class="modal-title">跟进方式说明</view>
<view class="modal-body">
<view class="modal-text">待办生成待办单需员工手动进行处理</view>
<view class="modal-text">群发生成群发单员工可批量进行处理wxapp mock 不接入群发</view>
</view>
<view class="modal-actions">
<view class="modal-btn save" @click="closeInfo">关闭</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { computed, reactive, ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import dayjs from 'dayjs';
import { ensureSeed, upsertFollowup } from '@/components/archive-detail/mock';
const archiveId = ref('');
const archiveName = ref('');
const eventTypeList = [
{ label: '回访', value: 'followup' },
{ label: '复诊提醒', value: 'revisit' },
{ label: '问卷', value: 'questionnaire' },
{ label: '其他', value: 'other' },
];
const teamOptions = [
{ label: '口腔一科(示例)', value: 'team_1' },
{ label: '正畸团队(示例)', value: 'team_2' },
];
const form = reactive({
planExecutionTime: '',
executeTeamId: '',
executeTeamName: '',
executorName: '',
eventType: '',
taskContent: '',
executeMethod: 'todo', // todo | groupMessage
sendContent: '',
});
const eventTypeLabel = computed(() => eventTypeList.find((i) => i.value === form.eventType)?.label || '');
onLoad((options) => {
archiveId.value = options?.archiveId ? String(options.archiveId) : '';
const c = uni.getStorageSync('new-followup-customer');
if (c && typeof c === 'object') {
archiveId.value = archiveId.value || String(c._id || '');
archiveName.value = String(c.name || '');
}
if (!archiveId.value) {
uni.showToast({ title: '缺少 archiveId', icon: 'none' });
setTimeout(() => uni.navigateBack(), 300);
return;
}
ensureSeed(archiveId.value, { name: archiveName.value });
// 使用模板:从 plan-list 选择后写入 select-mamagement-plan
const plan = uni.getStorageSync('select-mamagement-plan');
if (plan && typeof plan === 'object' && plan.planName) {
form.eventType = plan.eventType || 'followup';
form.taskContent = plan.taskContent || `执行回访计划:${plan.planName}`;
uni.setNavigationBarTitle({ title: '使用模板新增任务' });
}
if (!form.planExecutionTime) form.planExecutionTime = dayjs().add(1, 'day').format('YYYY-MM-DD');
});
function changeDate(e) {
const date = String(e.detail.value || '');
if (dayjs().startOf('day').isAfter(dayjs(date))) {
uni.showToast({ title: '请选择有效的回访日期', icon: 'none' });
return;
}
form.planExecutionTime = date;
}
function selectType() {
uni.showActionSheet({
itemList: eventTypeList.map((i) => i.label),
success: ({ tapIndex }) => {
form.eventType = eventTypeList[tapIndex]?.value || '';
},
});
}
function selectExecutor() {
// wxapp 先用 mock选择团队 + 固定执行人
uni.showActionSheet({
itemList: teamOptions.map((i) => i.label),
success: ({ tapIndex }) => {
const t = teamOptions[tapIndex];
form.executeTeamId = t.value;
form.executeTeamName = t.label;
form.executorName = '李医生';
},
});
}
function cancel() {
uni.showModal({
title: '确认取消',
content: '确定要取消新建回访任务吗?',
success: (res) => res.confirm && uni.navigateBack(),
});
}
function save() {
if (!form.planExecutionTime) return uni.showToast({ title: '请选择回访日期', icon: 'none' });
if (!form.executorName) return uni.showToast({ title: '请选择处理人', icon: 'none' });
if (!form.eventType) return uni.showToast({ title: '请选择类型', icon: 'none' });
if (!String(form.taskContent || '').trim()) return uni.showToast({ title: '请输入目的', icon: 'none' });
if (form.executeMethod === 'groupMessage' && !String(form.sendContent || '').trim()) {
return uni.showToast({ title: '请输入发送内容', icon: 'none' });
}
const plannedExecutionTime = dayjs(form.planExecutionTime).valueOf();
upsertFollowup({
archiveId: archiveId.value,
followup: {
plannedExecutionTime,
status: 'processing',
eventStatusLabel: '待处理',
eventType: form.eventType,
eventTypeLabel: eventTypeLabel.value || '回访',
executeTeamId: form.executeTeamId || 'team_1',
executeTeamName: form.executeTeamName || '口腔一科(示例)',
executorName: form.executorName,
creatorName: '我',
taskContent: form.taskContent,
result: '',
executeMethod: form.executeMethod,
sendContent: form.executeMethod === 'groupMessage' ? form.sendContent : '',
createTime: Date.now(),
},
});
uni.$emit('archive-detail:followup-changed');
uni.showToast({ title: '保存成功', icon: 'success' });
setTimeout(() => uni.navigateBack(), 300);
}
const infoPopup = ref(null);
function showInfo() {
infoPopup.value?.open?.();
}
function closeInfo() {
infoPopup.value?.close?.();
}
</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;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}
.row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 14px;
border-bottom: 1px solid #f2f2f2;
}
.row.clickable:active {
background: #fafafa;
}
.left {
display: flex;
align-items: center;
}
.label {
font-size: 14px;
color: #666;
}
.req {
margin-left: 6px;
color: #ff4d4f;
font-size: 14px;
}
.right {
display: flex;
align-items: center;
gap: 10px;
}
.value {
font-size: 14px;
color: #333;
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.value.muted {
color: #999;
}
.block {
padding: 14px 14px;
border-bottom: 1px solid #f2f2f2;
}
.block:last-child {
border-bottom: none;
}
.block-title {
font-size: 14px;
color: #666;
margin-bottom: 10px;
}
.textarea-box {
border: 1px solid #e6e6e6;
border-radius: 8px;
padding: 10px;
}
.textarea {
width: 100%;
min-height: 90px;
font-size: 14px;
box-sizing: border-box;
}
.counter {
margin-top: 6px;
text-align: right;
font-size: 12px;
color: #999;
}
.toggle-row {
display: flex;
align-items: center;
gap: 10px;
}
.pill {
padding: 8px 12px;
border-radius: 999px;
background: #eaecef;
font-size: 12px;
color: #333;
}
.pill.active {
background: #4f6ef7;
color: #fff;
}
.info {
margin-left: auto;
width: 18px;
height: 18px;
border-radius: 9px;
background: #cfd3dc;
color: #fff;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.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;
}
.modal {
width: 320px;
background: #fff;
border-radius: 8px;
overflow: hidden;
}
.modal-title {
font-size: 16px;
font-weight: 600;
text-align: center;
padding: 14px 12px;
color: #333;
border-bottom: 1px solid #f0f0f0;
}
.modal-body {
padding: 14px;
}
.modal-text {
font-size: 14px;
color: #666;
line-height: 20px;
margin-bottom: 10px;
}
.modal-actions {
padding: 12px 14px 14px;
}
.modal-btn {
height: 40px;
line-height: 40px;
text-align: center;
border-radius: 4px;
font-size: 14px;
}
.modal-btn.save {
background: #4f6ef7;
color: #fff;
}
</style>