This commit is contained in:
Your Name
2026-08-06 16:29:08 +08:00
parent a010483bdc
commit 8bbd6f7885
341 changed files with 768 additions and 317 deletions
+12
View File
@@ -125,6 +125,18 @@ export function myPatientCancelAppointment(params: { id: number }) {
return request.post({ url: '/firstvisit.myPatient/cancelAppointment', params })
}
export function myPatientAssistants() {
return request.get({ url: '/firstvisit.myPatient/assistants' })
}
export function myPatientAssign(params: { id: number; assistant_id: number; is_inherit?: 0 | 1 }) {
return request.post({ url: '/firstvisit.myPatient/assign', params })
}
export function myPatientFillIdCard(params: { id: number; id_card: string }) {
return request.post({ url: '/firstvisit.myPatient/fillIdCard', params })
}
export interface FirstVisitConversionParams {
time_type: 'today' | 'yesterday' | 'week' | 'month' | 'quarter' | 'year'
dept_id?: number
@@ -146,12 +146,28 @@
<el-table-column label="诊单日期" width="118">
<template #default="{ row }">{{ row.diagnosis_date_text || '—' }}</template>
</el-table-column>
<el-table-column label="操作" min-width="285" fixed="right">
<el-table-column label="操作" min-width="430" fixed="right">
<template #default="{ row }">
<div class="row-actions">
<el-button v-if="canEditDiagnosis" type="primary" link @click="openDiagnosis(row)">诊单</el-button>
<el-button v-else-if="canReadDiagnosis" type="primary" link @click="openReadonlyDiagnosis(row)">查看</el-button>
<el-button v-if="canBookAppointment" type="primary" link @click="openAppointment(row)">预约</el-button>
<el-button
v-if="canAssignPatient"
type="warning"
link
@click="openAssignDialog(row)"
>
{{ Number(row.assistant_id) > 0 ? '重新指派' : '指派' }}
</el-button>
<el-button
v-if="canFillIdCard && !Number(row.has_id_card)"
type="warning"
link
@click="openFillIdCardDialog(row)"
>
补全身份证
</el-button>
<el-button
v-if="canShowDiagnosisQRCode(row)"
type="primary"
@@ -196,6 +212,84 @@
<edit-popup ref="editRef" @success="refreshPage" />
<appointment-popup ref="appointmentRef" api-scene="my_patient" @success="refreshPage" />
<el-dialog
v-model="assignDialogVisible"
title="指派医助"
width="500px"
:close-on-click-modal="false"
>
<el-form :model="assignForm" label-width="88px">
<el-form-item label="患者">
<span class="dialog-patient-name">{{ currentActionPatient?.patient_name || '—' }}</span>
</el-form-item>
<el-form-item label="当前助理">
<span>{{ currentActionPatient?.assistant_name || '未分配' }}</span>
</el-form-item>
<el-form-item label="选择医助" required>
<el-select
v-model="assignForm.assistant_id"
class="dialog-full-width"
filterable
clearable
:loading="assistantOptionsLoading"
placeholder="请选择当前范围内的医助"
>
<el-option
v-for="item in assistantOptions"
:key="item.id"
:label="assistantOptionLabel(item)"
:value="Number(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item label="继承">
<div class="inherit-field">
<el-checkbox v-model="assignForm.is_inherit">作为继承指派</el-checkbox>
<small>用于区分继承关系指派操作仍会完整记录</small>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="assignDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="assignLoading" @click="submitAssign">确定指派</el-button>
</template>
</el-dialog>
<el-dialog
v-model="fillIdCardDialogVisible"
title="补全身份证号"
width="430px"
:close-on-click-modal="false"
@closed="resetFillIdCardForm"
>
<el-form
ref="fillIdCardFormRef"
:model="fillIdCardForm"
:rules="fillIdCardRules"
label-width="88px"
>
<el-form-item label="患者">
<span class="dialog-patient-name">{{ fillIdCardForm.patient_name || '—' }}</span>
</el-form-item>
<el-form-item label="身份证号" prop="id_card">
<el-input
v-model="fillIdCardForm.id_card"
maxlength="18"
clearable
show-word-limit
placeholder="请输入15或18位身份证号"
@keyup.enter="submitFillIdCard"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="fillIdCardDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="fillIdCardLoading" @click="submitFillIdCard">
提交并更新年龄
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="qrcodeDialogVisible"
title="诊单二维码"
@@ -233,7 +327,13 @@ import { Calendar, Clock, Loading, Lock, Refresh, Search } from '@element-plus/i
import { usePaging } from '@/hooks/usePaging'
import { hasPermission } from '@/utils/perm'
import feedback from '@/utils/feedback'
import { myPatientCancelAppointment, myPatientLists } from '@/api/first_visit'
import {
myPatientAssign,
myPatientAssistants,
myPatientCancelAppointment,
myPatientFillIdCard,
myPatientLists
} from '@/api/first_visit'
import { generateMiniProgramQrcode } from '@/api/tcm'
import { getWeappConfig } from '@/api/channel/weapp'
import useUserStore from '@/stores/modules/user'
@@ -259,6 +359,23 @@ const qrcodeDialogVisible = ref(false)
const qrcodeLoading = ref(false)
const qrcodeUrl = ref('')
const currentQRCodePatient = ref<any>(null)
const assignDialogVisible = ref(false)
const assignLoading = ref(false)
const assistantOptionsLoading = ref(false)
const assistantOptions = ref<any[]>([])
const currentActionPatient = ref<any>(null)
const assignForm = reactive({
assistant_id: null as number | null,
is_inherit: false
})
const fillIdCardDialogVisible = ref(false)
const fillIdCardLoading = ref(false)
const fillIdCardFormRef = ref<any>()
const fillIdCardForm = reactive({
id: 0,
patient_name: '',
id_card: ''
})
const formData = reactive({
keyword: '',
@@ -301,6 +418,8 @@ const scopeLabel = computed(() => pager.extend?.scope?.label || '按权限加载
const canEditDiagnosis = computed(() => hasPermission(['tcm.diagnosis/edit']))
const canReadDiagnosis = computed(() => hasPermission(['tcm.diagnosis/readonlyDetail']))
const canBookAppointment = computed(() => hasPermission(['tcm.diagnosis/guahao']))
const canAssignPatient = computed(() => hasPermission(['tcm.diagnosis/assign']))
const canFillIdCard = computed(() => hasPermission(['tcm.diagnosis/edit']))
const workspaceLoading = computed(() => {
if (activeWorkspace.value === 'orders') return Boolean(orderPanelRef.value?.loading)
if (activeWorkspace.value === 'progress') return Boolean(progressPanelRef.value?.loading)
@@ -399,6 +518,115 @@ function openAppointment(row: any) {
})
}
function assistantOptionLabel(item: any) {
const name = String(item?.name || item?.account || `医助${item?.id || ''}`)
const departments = String(item?.dept_names || '').trim()
return departments ? `${name} · ${departments}` : name
}
async function loadAssistantOptions() {
assistantOptionsLoading.value = true
try {
const result = await myPatientAssistants()
assistantOptions.value = Array.isArray(result) ? result : []
} catch (error: any) {
assistantOptions.value = []
feedback.msgError(error?.msg || '医助列表加载失败')
} finally {
assistantOptionsLoading.value = false
}
}
async function openAssignDialog(row: any) {
currentActionPatient.value = row
assignForm.assistant_id = Number(row.assistant_id) > 0 ? Number(row.assistant_id) : null
assignForm.is_inherit = false
assignDialogVisible.value = true
if (assistantOptions.value.length === 0) {
await loadAssistantOptions()
}
}
async function submitAssign() {
const diagnosisId = Number(currentActionPatient.value?.diagnosis_id || currentActionPatient.value?.id)
if (diagnosisId <= 0) {
feedback.msgWarning('患者诊单信息不完整')
return
}
if (!assignForm.assistant_id) {
feedback.msgWarning('请选择医助')
return
}
assignLoading.value = true
try {
await myPatientAssign({
id: diagnosisId,
assistant_id: Number(assignForm.assistant_id),
is_inherit: assignForm.is_inherit ? 1 : 0
})
feedback.msgSuccess('指派成功')
assignDialogVisible.value = false
await getLists()
} catch (error: any) {
feedback.msgError(error?.msg || '指派失败')
} finally {
assignLoading.value = false
}
}
const validateIdCard = (_rule: any, value: string, callback: (error?: Error) => void) => {
const idCard = String(value || '').trim()
if (!idCard) {
callback(new Error('请输入身份证号'))
return
}
const valid18 = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(idCard)
const valid15 = /^[1-9]\d{5}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}$/.test(idCard)
callback(valid18 || valid15 ? undefined : new Error('请输入15或18位有效身份证号'))
}
const fillIdCardRules = {
id_card: [{ required: true, validator: validateIdCard, trigger: 'blur' }]
}
function openFillIdCardDialog(row: any) {
fillIdCardForm.id = Number(row.diagnosis_id || row.id)
fillIdCardForm.patient_name = String(row.patient_name || '')
fillIdCardForm.id_card = ''
fillIdCardDialogVisible.value = true
}
function resetFillIdCardForm() {
fillIdCardForm.id = 0
fillIdCardForm.patient_name = ''
fillIdCardForm.id_card = ''
fillIdCardFormRef.value?.clearValidate?.()
}
async function submitFillIdCard() {
if (fillIdCardLoading.value) return
try {
await fillIdCardFormRef.value?.validate?.()
} catch {
return
}
fillIdCardLoading.value = true
try {
await myPatientFillIdCard({
id: fillIdCardForm.id,
id_card: fillIdCardForm.id_card.trim()
})
feedback.msgSuccess('补全成功,年龄已自动更新')
fillIdCardDialogVisible.value = false
await getLists()
} catch (error: any) {
feedback.msgError(error?.msg || '补全身份证失败')
} finally {
fillIdCardLoading.value = false
}
}
function canShowDiagnosisQRCode(row: any) {
return canBookAppointment.value
&& Number(row.appointment_id) > 0
@@ -850,6 +1078,25 @@ onMounted(() => {
}
}
.dialog-full-width {
width: 100%;
}
.dialog-patient-name {
color: #1f2937;
font-weight: 600;
}
.inherit-field {
display: grid;
gap: 2px;
small {
color: #98a2b3;
line-height: 1.5;
}
}
.pagination-wrap {
display: flex;
justify-content: flex-end;
@@ -66,6 +66,22 @@
</div>
</article>
<article class="metric-card">
<div class="metric-label">今日新增业绩</div>
<div class="metric-value metric-value--money">
{{ formatMoney(dashboard.performance.today_amount) }}
</div>
<div class="metric-foot" :class="comparisonClass(dashboard.performance.today_compare_rate)">
<template v-if="dashboard.performance.today_compare_rate !== null">
<el-icon v-if="dashboard.performance.today_compare_rate >= 0"><CaretTop /></el-icon>
<el-icon v-else><CaretBottom /></el-icon>
{{ dashboard.performance.today_compare_label }}
{{ formatSignedPercent(dashboard.performance.today_compare_rate) }}
</template>
<template v-else>昨日暂无可比数据</template>
</div>
</article>
<article class="metric-card">
<div class="metric-label">昨日新增业绩</div>
<div class="metric-value metric-value--money">
@@ -336,6 +352,9 @@ const createInitialDashboard = () => ({
month_amount: 0,
month_compare_rate: null as number | null,
month_compare_label: '',
today_amount: 0,
today_compare_rate: null as number | null,
today_compare_label: '',
yesterday_amount: 0,
yesterday_compare_rate: null as number | null,
yesterday_compare_label: '',
@@ -347,6 +366,7 @@ const createInitialDashboard = () => ({
interview_count: 0,
completed_order_count: 0,
completed_order_amount: 0,
paid_appointment_count: 0,
paid_appointment_rate: 0,
interview_receive_rate: 0,
comparisons: {
@@ -447,7 +467,7 @@ const todayMetrics = computed(() => [
key: 'appointmentRate',
label: '付费挂号率',
value: formatPercent(dashboard.today.paid_appointment_rate),
hint: '付费挂号数 / 加粉数',
hint: `付费挂号 ${formatNumber(dashboard.today.paid_appointment_count)} / 加粉数`,
comparisons: [dashboard.today.comparisons.paid_appointment_rate],
},
{
@@ -750,7 +770,7 @@ onBeforeUnmount(() => {
}
.performance-strip {
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-columns: repeat(5, minmax(0, 1fr));
margin-bottom: 12px;
}