更新
This commit is contained in:
@@ -67,6 +67,8 @@
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">日期</span>
|
||||
<el-radio-group v-model="formData.date_preset" size="small" @change="handleDatePresetChange" class="filter-radio">
|
||||
<el-radio-button value="yesterday">昨天挂号</el-radio-button>
|
||||
<el-radio-button value="day_before">前天挂号</el-radio-button>
|
||||
<el-radio-button value="today">当天挂号</el-radio-button>
|
||||
<el-radio-button value="tomorrow">明天挂号</el-radio-button>
|
||||
<el-radio-button value="day_after">后天挂号</el-radio-button>
|
||||
@@ -153,10 +155,20 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="开方" width="80" align="center">
|
||||
<el-table-column label="开方" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.has_prescription" type="success" size="small" effect="plain">已开方</el-tag>
|
||||
<el-tag v-else type="info" size="small" effect="plain">未开方</el-tag>
|
||||
<div class="prescription-tags">
|
||||
<el-tag v-if="row.has_prescription" type="success" size="small" effect="plain">已开方</el-tag>
|
||||
<el-tag v-else type="info" size="small" effect="plain">未开方</el-tag>
|
||||
<el-tag
|
||||
v-if="row.has_prescription && row.prescription_is_system_auto === 1"
|
||||
type="warning"
|
||||
size="small"
|
||||
effect="plain"
|
||||
>
|
||||
系统代开
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -561,7 +573,7 @@ const formData = reactive({
|
||||
status: '' as string | number,
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
date_preset: 'today' as '' | 'today' | 'tomorrow' | 'day_after',
|
||||
date_preset: 'today' as '' | 'yesterday' | 'day_before' | 'today' | 'tomorrow' | 'day_after',
|
||||
diagnosis_confirmed: '' as '' | '0' | '1', // ''=全部 1=已确认 0=未确认
|
||||
/** 为 1 时后端 extend 返回各状态数量,避免额外 4 次列表请求 */
|
||||
include_status_counts: 0 as 0 | 1
|
||||
@@ -589,14 +601,31 @@ watch(
|
||||
if (!formData.date_preset) return
|
||||
const today = new Date()
|
||||
const pad = (n: number) => String(n).padStart(2, '0')
|
||||
const todayStr = `${today.getFullYear()}-${pad(today.getMonth() + 1)}-${pad(today.getDate())}`
|
||||
const fmt = (d: Date) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
||||
const todayStr = fmt(today)
|
||||
const yesterday = new Date(today)
|
||||
yesterday.setDate(yesterday.getDate() - 1)
|
||||
const yesterdayStr = fmt(yesterday)
|
||||
const dayBefore = new Date(today)
|
||||
dayBefore.setDate(dayBefore.getDate() - 2)
|
||||
const dayBeforeStr = fmt(dayBefore)
|
||||
const tomorrow = new Date(today)
|
||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||
const tomorrowStr = `${tomorrow.getFullYear()}-${pad(tomorrow.getMonth() + 1)}-${pad(tomorrow.getDate())}`
|
||||
const tomorrowStr = fmt(tomorrow)
|
||||
const dayAfter = new Date(today)
|
||||
dayAfter.setDate(dayAfter.getDate() + 2)
|
||||
const dayAfterStr = `${dayAfter.getFullYear()}-${pad(dayAfter.getMonth() + 1)}-${pad(dayAfter.getDate())}`
|
||||
const expected = formData.date_preset === 'today' ? todayStr : formData.date_preset === 'tomorrow' ? tomorrowStr : dayAfterStr
|
||||
const dayAfterStr = fmt(dayAfter)
|
||||
const preset = formData.date_preset
|
||||
const expected =
|
||||
preset === 'yesterday'
|
||||
? yesterdayStr
|
||||
: preset === 'day_before'
|
||||
? dayBeforeStr
|
||||
: preset === 'today'
|
||||
? todayStr
|
||||
: preset === 'tomorrow'
|
||||
? tomorrowStr
|
||||
: dayAfterStr
|
||||
const startStr = (start || '').split(' ')[0]
|
||||
if (startStr !== expected) {
|
||||
formData.date_preset = ''
|
||||
@@ -677,7 +706,13 @@ const handleDatePresetChange = (val: string | number | boolean | undefined) => {
|
||||
const today = new Date()
|
||||
const pad = (n: number) => String(n).padStart(2, '0')
|
||||
let target: Date
|
||||
if (v === 'today') {
|
||||
if (v === 'yesterday') {
|
||||
target = new Date(today)
|
||||
target.setDate(target.getDate() - 1)
|
||||
} else if (v === 'day_before') {
|
||||
target = new Date(today)
|
||||
target.setDate(target.getDate() - 2)
|
||||
} else if (v === 'today') {
|
||||
target = today
|
||||
} else if (v === 'tomorrow') {
|
||||
target = new Date(today)
|
||||
@@ -687,7 +722,7 @@ const handleDatePresetChange = (val: string | number | boolean | undefined) => {
|
||||
target.setDate(target.getDate() + 2)
|
||||
}
|
||||
const dateStr = `${target.getFullYear()}-${pad(target.getMonth() + 1)}-${pad(target.getDate())}`
|
||||
formData.date_preset = v as '' | 'today' | 'tomorrow' | 'day_after'
|
||||
formData.date_preset = v as '' | 'yesterday' | 'day_before' | 'today' | 'tomorrow' | 'day_after'
|
||||
formData.start_date = dateStr
|
||||
formData.end_date = dateStr
|
||||
pager.page = 1
|
||||
@@ -1144,6 +1179,14 @@ onUnmounted(() => {
|
||||
padding: 0 16px 20px;
|
||||
}
|
||||
|
||||
.prescription-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -50,7 +50,26 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<template v-if="showPhoneMaskedEdit">
|
||||
<el-input
|
||||
:model-value="
|
||||
phoneRevealUnlocked ? originalPhone || '' : maskPhone(originalPhone || '')
|
||||
"
|
||||
readonly
|
||||
placeholder="点击可查看完整手机号"
|
||||
maxlength="11"
|
||||
class="cursor-pointer phone-mask-toggle"
|
||||
@click="togglePhoneReveal"
|
||||
/>
|
||||
<p
|
||||
v-if="originalPhone && !phoneRevealUnlocked"
|
||||
class="text-xs text-gray-400 mt-1"
|
||||
>
|
||||
当前为脱敏显示,点击输入框可查看完整号码
|
||||
</p>
|
||||
</template>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="formData.phone"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
@@ -695,6 +714,12 @@ const visible = ref(false)
|
||||
const formRef = ref()
|
||||
const mode = ref('add')
|
||||
const submitting = ref(false)
|
||||
|
||||
/** 拥有后手机号可正常编辑,失焦不再强制脱敏 */
|
||||
const hasPhonePlainPermission = computed(() => hasPermission(['tcm.diagnosis/phonePlain']))
|
||||
/** 编辑且无明文权限:只读脱敏,点击切换查看完整号 */
|
||||
const showPhoneMaskedEdit = computed(() => mode.value === 'edit' && !hasPhonePlainPermission.value)
|
||||
const phoneRevealUnlocked = ref(false)
|
||||
const activeTab = ref('basic')
|
||||
const drawerTitle = computed(() => (mode.value === 'add' ? '新增诊单' : '编辑诊单'))
|
||||
|
||||
@@ -803,6 +828,11 @@ const maskPhone = (phone: string) => {
|
||||
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||||
}
|
||||
|
||||
const togglePhoneReveal = () => {
|
||||
if (!originalPhone.value) return
|
||||
phoneRevealUnlocked.value = !phoneRevealUnlocked.value
|
||||
}
|
||||
|
||||
const maskIdCard = (idCard: string) => {
|
||||
if (!idCard) return idCard
|
||||
if (idCard.length === 15) {
|
||||
@@ -813,30 +843,55 @@ const maskIdCard = (idCard: string) => {
|
||||
return idCard
|
||||
}
|
||||
|
||||
// 手机号聚焦 - 显示完整数据
|
||||
// 手机号聚焦 - 显示完整数据(无「明文」权限时仅新增模式在失焦后可再次展开编辑)
|
||||
const handlePhoneFocus = () => {
|
||||
isPhoneFocused.value = true
|
||||
if (originalPhone.value) {
|
||||
if (!originalPhone.value) return
|
||||
if (hasPhonePlainPermission.value) {
|
||||
formData.value.phone = originalPhone.value
|
||||
return
|
||||
}
|
||||
if (mode.value === 'add') {
|
||||
formData.value.phone = originalPhone.value
|
||||
}
|
||||
}
|
||||
|
||||
// 手机号失焦 - 恢复脱敏并验证
|
||||
// 手机号失焦:有明文权限则保持明文并校验;新增且无明文权限时仍脱敏展示
|
||||
const handlePhoneBlur = async () => {
|
||||
isPhoneFocused.value = false
|
||||
|
||||
// 保存原始数据并脱敏显示
|
||||
|
||||
if (hasPhonePlainPermission.value) {
|
||||
if (formData.value.phone && formData.value.phone.length === 11) {
|
||||
originalPhone.value = formData.value.phone
|
||||
if (/^1[3-9]\d{9}$/.test(formData.value.phone)) {
|
||||
try {
|
||||
const result = await checkPhone({
|
||||
phone: formData.value.phone,
|
||||
id: formData.value.id || ''
|
||||
})
|
||||
if (result.exists) {
|
||||
ElMessage.warning(result.message)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查手机号失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (mode.value !== 'add') {
|
||||
return
|
||||
}
|
||||
|
||||
if (formData.value.phone && formData.value.phone.length === 11) {
|
||||
originalPhone.value = formData.value.phone
|
||||
|
||||
// 验证手机号唯一性
|
||||
if (/^1[3-9]\d{9}$/.test(formData.value.phone)) {
|
||||
try {
|
||||
const result = await checkPhone({
|
||||
phone: formData.value.phone,
|
||||
id: formData.value.id || ''
|
||||
})
|
||||
|
||||
if (result.exists) {
|
||||
ElMessage.warning(result.message)
|
||||
}
|
||||
@@ -844,8 +899,6 @@ const handlePhoneBlur = async () => {
|
||||
console.error('检查手机号失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 脱敏显示
|
||||
formData.value.phone = maskPhone(formData.value.phone)
|
||||
}
|
||||
}
|
||||
@@ -1026,21 +1079,22 @@ const open = async (type: string, id?: number) => {
|
||||
mode.value = type
|
||||
visible.value = true
|
||||
activeTab.value = 'basic' // 重置到基本信息标签页
|
||||
|
||||
phoneRevealUnlocked.value = false
|
||||
|
||||
// 加载字典数据
|
||||
await getDictOptions()
|
||||
|
||||
|
||||
if (type === 'edit' && id) {
|
||||
const data = await tcmDiagnosisDetail({ id })
|
||||
|
||||
// 保存原始完整数据
|
||||
originalPhone.value = data.phone || ''
|
||||
originalIdCard.value = data.id_card || ''
|
||||
|
||||
// 显示脱敏数据
|
||||
data.phone = maskPhone(data.phone || '')
|
||||
|
||||
// 有「手机号明文」权限:表单中保持完整号码;否则表单内为脱敏(编辑无权限时用只读区+点击展开)
|
||||
data.phone = hasPhonePlainPermission.value ? data.phone || '' : maskPhone(data.phone || '')
|
||||
data.id_card = maskIdCard(data.id_card || '')
|
||||
|
||||
|
||||
formData.value = data
|
||||
const y = data.diabetes_discovery_year
|
||||
formData.value.diabetes_discovery_year =
|
||||
@@ -1069,8 +1123,9 @@ const handleSubmit = async () => {
|
||||
// 如果是新增,保存成功后切换到编辑模式,这样可以添加血糖血压记录
|
||||
if (result && result.id) {
|
||||
mode.value = 'edit'
|
||||
phoneRevealUnlocked.value = false
|
||||
formData.value.id = result.id
|
||||
|
||||
|
||||
// 重新获取详情,确保patient_id等字段正确
|
||||
try {
|
||||
const detail = await tcmDiagnosisDetail({ id: result.id })
|
||||
@@ -1079,9 +1134,10 @@ const handleSubmit = async () => {
|
||||
// 更新原始数据
|
||||
originalPhone.value = detail.phone || ''
|
||||
originalIdCard.value = detail.id_card || ''
|
||||
|
||||
// 显示脱敏数据
|
||||
formData.value.phone = maskPhone(detail.phone || '')
|
||||
|
||||
formData.value.phone = hasPhonePlainPermission.value
|
||||
? detail.phone || ''
|
||||
: maskPhone(detail.phone || '')
|
||||
formData.value.id_card = maskIdCard(detail.id_card || '')
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error)
|
||||
@@ -1110,7 +1166,7 @@ const handleOpenPrescription = () => {
|
||||
diagnosis_id: formData.value.id,
|
||||
appointment_id: 0,
|
||||
patient_name: formData.value.patient_name,
|
||||
patient_phone: formData.value.phone,
|
||||
patient_phone: originalPhone.value || formData.value.phone,
|
||||
patient_gender: formData.value.gender,
|
||||
patient_age: formData.value.age,
|
||||
case_record: JSON.parse(JSON.stringify(formData.value))
|
||||
@@ -1182,7 +1238,8 @@ const handleClose = () => {
|
||||
originalIdCard.value = ''
|
||||
isPhoneFocused.value = false
|
||||
isIdCardFocused.value = false
|
||||
|
||||
phoneRevealUnlocked.value = false
|
||||
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,35 @@
|
||||
<span
|
||||
v-for="tab in dateTabs"
|
||||
:key="tab.value"
|
||||
:class="['chip', { active: formData.pending_assign !== '1' && formData.appointment_date === tab.value }]"
|
||||
:class="[
|
||||
'chip',
|
||||
{
|
||||
active:
|
||||
formData.pending_assign !== '1' &&
|
||||
formData.pending_booking !== '1' &&
|
||||
formData.completed_appointment !== '1' &&
|
||||
formData.appointment_date === tab.value
|
||||
}
|
||||
]"
|
||||
@click="handleDateTabClick(tab.value)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
<em v-if="tab.value && dateCounts[tab.value] !== undefined">{{ dateCounts[tab.value] }}</em>
|
||||
</span>
|
||||
<span
|
||||
:class="['chip', 'chip-pending-booking', { active: formData.pending_booking === '1' }]"
|
||||
@click="handlePendingBookingTabClick"
|
||||
>
|
||||
待预约
|
||||
<em v-if="pendingBookingCount !== undefined">{{ pendingBookingCount }}</em>
|
||||
</span>
|
||||
<span
|
||||
:class="['chip', 'chip-completed-visit', { active: formData.completed_appointment === '1' }]"
|
||||
@click="handleCompletedVisitTabClick"
|
||||
>
|
||||
已完成
|
||||
<em v-if="completedVisitCount !== undefined">{{ completedVisitCount }}</em>
|
||||
</span>
|
||||
<span
|
||||
:class="['chip', 'chip-pending', { active: formData.pending_assign === '1' }]"
|
||||
@click="handlePendingAssignTabClick"
|
||||
@@ -584,7 +607,11 @@ const formData = reactive({
|
||||
diagnosis_confirmed: '' as '' | '0' | '1',
|
||||
appointment_date: '' as string,
|
||||
has_appointment: '' as '' | '0' | '1',
|
||||
pending_assign: '' as '' | '1'
|
||||
pending_assign: '' as '' | '1',
|
||||
/** 顶部「待预约」Tab:仅展示已建诊单且尚未挂号(无有效预约) */
|
||||
pending_booking: '' as '' | '1',
|
||||
/** 顶部「已完成」Tab:至少有一条挂号记录为已完成 status=3 */
|
||||
completed_appointment: '' as '' | '1'
|
||||
})
|
||||
|
||||
const activeTab = ref('all')
|
||||
@@ -603,6 +630,8 @@ const confirmedOptions = [
|
||||
|
||||
const setHasAppointment = (v: string) => {
|
||||
formData.has_appointment = v as '' | '0' | '1'
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = ''
|
||||
pager.page = 1
|
||||
getLists()
|
||||
fetchDateCounts()
|
||||
@@ -610,6 +639,8 @@ const setHasAppointment = (v: string) => {
|
||||
|
||||
const setConfirmed = (v: string) => {
|
||||
formData.diagnosis_confirmed = v as '' | '0' | '1'
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = ''
|
||||
pager.page = 1
|
||||
getLists()
|
||||
fetchDateCounts()
|
||||
@@ -630,31 +661,65 @@ const dateTabs = computed(() => [
|
||||
])
|
||||
|
||||
const dateCounts = ref<Record<string, number>>({})
|
||||
const pendingBookingCount = ref<number | undefined>(undefined)
|
||||
const completedVisitCount = ref<number | undefined>(undefined)
|
||||
const pendingAssignCount = ref<number | undefined>(undefined)
|
||||
|
||||
const handleDateTabClick = async (value: string) => {
|
||||
formData.pending_assign = ''
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = ''
|
||||
formData.has_appointment = ''
|
||||
formData.appointment_date = value
|
||||
pager.page = 1
|
||||
await getLists()
|
||||
fetchDateCounts()
|
||||
}
|
||||
|
||||
/** 已创建诊单、尚未挂号的记录(与第二行「未挂号」同条件,便于从 Tab 一键进入) */
|
||||
const handlePendingBookingTabClick = async () => {
|
||||
formData.pending_assign = ''
|
||||
formData.pending_booking = '1'
|
||||
formData.completed_appointment = ''
|
||||
formData.appointment_date = ''
|
||||
formData.has_appointment = '0'
|
||||
pager.page = 1
|
||||
await getLists()
|
||||
fetchDateCounts()
|
||||
}
|
||||
|
||||
/** 至少有一条挂号为「已完成」(后端 appointment.status=3) */
|
||||
const handleCompletedVisitTabClick = async () => {
|
||||
formData.pending_assign = ''
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = '1'
|
||||
formData.has_appointment = ''
|
||||
formData.appointment_date = ''
|
||||
pager.page = 1
|
||||
await getLists()
|
||||
fetchDateCounts()
|
||||
}
|
||||
|
||||
const handlePendingAssignTabClick = async () => {
|
||||
formData.pending_assign = '1'
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = ''
|
||||
formData.has_appointment = ''
|
||||
formData.appointment_date = ''
|
||||
pager.page = 1
|
||||
await getLists()
|
||||
fetchDateCounts()
|
||||
}
|
||||
|
||||
// 获取各日期挂号数量 + 待分配医助数量
|
||||
// 获取各日期挂号数量 + 待预约 + 已完成 + 待分配医助数量
|
||||
const fetchDateCounts = async () => {
|
||||
try {
|
||||
const [today, tomorrow, dayAfter, pending] = await Promise.all([
|
||||
const [today, tomorrow, dayAfter, noApt, doneVisit, pending] = await Promise.all([
|
||||
tcmDiagnosisLists({ appointment_date: todayStr.value, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ appointment_date: tomorrowStr.value, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ appointment_date: dayAfterStr.value, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ has_appointment: 0, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ completed_appointment: 1, page_no: 1, page_size: 1 }),
|
||||
tcmDiagnosisLists({ pending_assign: 1, page_no: 1, page_size: 1 })
|
||||
])
|
||||
dateCounts.value = {
|
||||
@@ -662,6 +727,8 @@ const fetchDateCounts = async () => {
|
||||
[tomorrowStr.value]: tomorrow?.count ?? 0,
|
||||
[dayAfterStr.value]: dayAfter?.count ?? 0
|
||||
}
|
||||
pendingBookingCount.value = noApt?.count ?? 0
|
||||
completedVisitCount.value = doneVisit?.count ?? 0
|
||||
pendingAssignCount.value = pending?.count ?? 0
|
||||
} catch (e) {
|
||||
console.error('获取日期数量失败', e)
|
||||
@@ -710,6 +777,8 @@ const handleTabChange = (tabName: string | number) => {
|
||||
formData.diagnosis_confirmed = ''
|
||||
formData.has_appointment = ''
|
||||
formData.appointment_date = ''
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = ''
|
||||
if (tabName === 'unconfirmed') {
|
||||
formData.diagnosis_confirmed = '0'
|
||||
} else if (tabName === 'confirmed') {
|
||||
@@ -790,6 +859,8 @@ const handleReset = () => {
|
||||
formData.appointment_date = ''
|
||||
formData.has_appointment = ''
|
||||
formData.pending_assign = ''
|
||||
formData.pending_booking = ''
|
||||
formData.completed_appointment = ''
|
||||
pager.page = 1
|
||||
getLists()
|
||||
fetchDateCounts()
|
||||
@@ -1517,6 +1588,36 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.chip-pending-booking {
|
||||
margin-left: 8px;
|
||||
color: var(--el-color-info);
|
||||
background: var(--el-color-info-light-9);
|
||||
|
||||
&:hover {
|
||||
background: var(--el-color-info-light-8);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
background: var(--el-color-info);
|
||||
}
|
||||
}
|
||||
|
||||
.chip-completed-visit {
|
||||
margin-left: 8px;
|
||||
color: var(--el-color-success);
|
||||
background: var(--el-color-success-light-9);
|
||||
|
||||
&:hover {
|
||||
background: var(--el-color-success-light-8);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
background: var(--el-color-success);
|
||||
}
|
||||
}
|
||||
|
||||
.chip-pending {
|
||||
margin-left: 8px;
|
||||
color: var(--el-color-warning);
|
||||
|
||||
Reference in New Issue
Block a user