更新
This commit is contained in:
@@ -15,6 +15,21 @@ export function qywxCustomerStats() {
|
||||
return request.get({ url: '/qywx.customer/stats' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 今日进入分布(事件流水零误差口径)
|
||||
* 返回 { total, recent_time, hourly: number[24], by_state: [{state,count}] }
|
||||
*/
|
||||
export function qywxCustomerTodayArrival() {
|
||||
return request.get({ url: '/qywx.customer/todayArrival' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 今日进入明细流水(每一次 add_external_contact 推送 = 一行)
|
||||
*/
|
||||
export function qywxCustomerTodayArrivalList(params: { page_no?: number; page_size?: number }) {
|
||||
return request.get({ url: '/qywx.customer/todayArrivalList', params })
|
||||
}
|
||||
|
||||
// 获取同步设置
|
||||
export function qywxSyncSettingsGet() {
|
||||
return request.get({ url: '/qywx.customer/getSyncSettings' })
|
||||
|
||||
@@ -98,9 +98,16 @@
|
||||
v-for="(herb, index) in formData.herbs"
|
||||
:key="'herb-' + index"
|
||||
class="herb-editor-card"
|
||||
:class="{ 'herb-editor-card--dup': isDuplicateHerbName(index) }"
|
||||
>
|
||||
<div class="herb-editor-card__title">中药 {{ index + 1 }}</div>
|
||||
<MedicineNameSelect v-model="herb.name" class="herb-editor-card__select" />
|
||||
<div
|
||||
v-if="isDuplicateHerbName(index)"
|
||||
class="herb-editor-card__dup-hint text-amber-600 text-xs mb-1"
|
||||
>
|
||||
该药名与其他行重复,请合并剂量或删除多余行
|
||||
</div>
|
||||
<div class="herb-editor-card__row">
|
||||
<el-input-number
|
||||
v-model="herb.dosage"
|
||||
@@ -547,15 +554,24 @@
|
||||
<el-icon><Document /></el-icon>
|
||||
下载PDF
|
||||
</el-button>
|
||||
<el-button
|
||||
<el-tooltip
|
||||
v-if="savedPrescription.void_status !== 1 && !approvedPreviewOnly"
|
||||
type="danger"
|
||||
plain
|
||||
:loading="voiding"
|
||||
@click="handleVoid"
|
||||
:disabled="!hasPrescriptionOrderBlockVoid"
|
||||
content="该处方已存在业务订单,无法作废,请前往已开处方里修改"
|
||||
placement="top"
|
||||
>
|
||||
作废处方
|
||||
</el-button>
|
||||
<span class="inline-block">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
:disabled="hasPrescriptionOrderBlockVoid"
|
||||
:loading="voiding"
|
||||
@click="handleVoid"
|
||||
>
|
||||
作废处方
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-button v-if="!approvedPreviewOnly" @click="handleNewPrescription">新建处方</el-button>
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
</div>
|
||||
@@ -714,6 +730,13 @@ const approvedPreviewOnly = computed(() => {
|
||||
return Number(s.audit_status) === 1 && Number(s.void_status) !== 1
|
||||
})
|
||||
|
||||
/** 已有关联业务订单(与列表 has_prescription_order 一致)则禁止作废 */
|
||||
const hasPrescriptionOrderBlockVoid = computed(() => {
|
||||
const s = savedPrescription.value
|
||||
if (!s) return false
|
||||
return Number(s.has_prescription_order) === 1
|
||||
})
|
||||
|
||||
const drawerTitle = computed(() => (approvedPreviewOnly.value ? '查看处方' : '中医处方单'))
|
||||
|
||||
// 处方库相关
|
||||
@@ -1084,6 +1107,33 @@ const initSignatureCanvas = () => {
|
||||
ctx.lineJoin = 'round'
|
||||
}
|
||||
|
||||
/** 药名去首尾空格后比较,避免「 黄芪 」与「黄芪」判成不同 */
|
||||
function normalizeHerbName(name: string | undefined): string {
|
||||
return (name ?? '').trim()
|
||||
}
|
||||
|
||||
/** 当前行药名是否与其他行重复(非空且出现次数大于 1) */
|
||||
function isDuplicateHerbName(index: number): boolean {
|
||||
const herbs = formData.herbs
|
||||
const key = normalizeHerbName(herbs[index]?.name)
|
||||
if (!key) return false
|
||||
let count = 0
|
||||
for (let i = 0; i < herbs.length; i++) {
|
||||
if (normalizeHerbName(herbs[i]?.name) === key) count++
|
||||
}
|
||||
return count > 1
|
||||
}
|
||||
|
||||
function findDuplicateHerbNames(): string[] {
|
||||
const map = new Map<string, number>()
|
||||
for (const h of formData.herbs) {
|
||||
const key = normalizeHerbName(h?.name)
|
||||
if (!key) continue
|
||||
map.set(key, (map.get(key) ?? 0) + 1)
|
||||
}
|
||||
return [...map.entries()].filter(([, c]) => c > 1).map(([name]) => name)
|
||||
}
|
||||
|
||||
const handleAddHerb = () => {
|
||||
formData.herbs.push({ name: '', dosage: 6 })
|
||||
}
|
||||
@@ -1132,6 +1182,10 @@ const handleImportLibrary = (row: any) => {
|
||||
const importedHerbs = JSON.parse(JSON.stringify(row.herbs))
|
||||
formData.herbs = importedHerbs
|
||||
|
||||
if (findDuplicateHerbNames().length) {
|
||||
feedback.msgWarning('导入的处方中存在重复药名,请合并剂量或删除多余行')
|
||||
}
|
||||
|
||||
feedback.msgSuccess(`已导入处方「${row.prescription_name}」,共${importedHerbs.length}味药材`)
|
||||
showLibraryDialog.value = false
|
||||
}
|
||||
@@ -1174,6 +1228,11 @@ const handleSave = async () => {
|
||||
feedback.msgWarning('请至少添加一味中药')
|
||||
return
|
||||
}
|
||||
const dupNames = findDuplicateHerbNames()
|
||||
if (dupNames.length) {
|
||||
feedback.msgWarning(`存在重复药名:${dupNames.join('、')},请合并剂量或删除多余行后再保存`)
|
||||
return
|
||||
}
|
||||
for (const h of formData.herbs) {
|
||||
if (!h.name?.trim()) {
|
||||
feedback.msgWarning('请填写中药名称')
|
||||
@@ -1252,6 +1311,10 @@ const formatVoidTime = (ts: number | null | undefined) => {
|
||||
const handleVoid = async () => {
|
||||
const id = savedPrescription.value?.id
|
||||
if (!id) return
|
||||
if (Number(savedPrescription.value?.has_prescription_order) === 1) {
|
||||
feedback.msgWarning('该处方已存在业务订单,无法作废')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await feedback.confirm('确定要作废该处方吗?作废后不可恢复。')
|
||||
voiding.value = true
|
||||
@@ -1453,6 +1516,11 @@ defineExpose({
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.herb-editor-card--dup {
|
||||
border-color: var(--el-color-warning);
|
||||
background: var(--el-color-warning-light-9);
|
||||
}
|
||||
|
||||
.herb-editor-card__title {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
|
||||
@@ -110,6 +110,19 @@
|
||||
<el-tag type="info" size="small">{{ row.prescription_type || '—' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来源" min-width="92" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-if="Number(row.is_system_auto) === 1"
|
||||
type="warning"
|
||||
size="small"
|
||||
effect="plain"
|
||||
>
|
||||
系统代开
|
||||
</el-tag>
|
||||
<span v-else class="text-gray-400 text-sm">手工</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="患者信息" min-width="110">
|
||||
<template #default="{ row }">
|
||||
<div class="font-medium">{{ row.patient_name || '—' }}</div>
|
||||
@@ -237,6 +250,13 @@
|
||||
<div class="cf-slip-header">
|
||||
<h2 class="cf-slip-title">{{ SLIP_HOSPITAL_NAME }}处方笺</h2>
|
||||
<div class="cf-slip-type">
|
||||
<el-tag
|
||||
v-if="Number(slipView.is_system_auto) === 1"
|
||||
type="warning"
|
||||
size="small"
|
||||
class="mr-1"
|
||||
effect="plain"
|
||||
>系统代开</el-tag>
|
||||
<el-tag v-if="Number(slipView.void_status) === 1" type="danger" size="small">已作废</el-tag>
|
||||
<el-tag
|
||||
v-else-if="Number(slipView.business_prescription_audit_rejected) === 1"
|
||||
@@ -364,6 +384,14 @@
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-alert
|
||||
v-if="editMode === 'edit' && Number(editForm.is_system_auto) === 1"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb-4"
|
||||
title="本处方为系统自动生成(如挂号完成),默认无药材配方,请补充药材与诊断等信息后再保存。"
|
||||
/>
|
||||
<el-alert
|
||||
v-if="editMode === 'edit'"
|
||||
type="info"
|
||||
@@ -505,6 +533,9 @@
|
||||
<el-button type="primary" size="small" @click="addHerb">
|
||||
添加药材
|
||||
</el-button>
|
||||
<el-button type="success" size="small" class="ml-2" @click="openLibraryDialog">
|
||||
从处方库导入
|
||||
</el-button>
|
||||
<el-table :data="editForm.herbs" class="mt-2" border>
|
||||
<el-table-column label="序号" type="index" width="60" />
|
||||
<el-table-column label="药材名称" min-width="150">
|
||||
@@ -816,9 +847,9 @@
|
||||
<span class="create-order-rx-card__patient">{{ createOrderPrescription.patient_name || '—' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-tooltip :content="createOrderHerbSummary" placement="bottom" :show-after="300">
|
||||
<!-- <el-tooltip :content="createOrderHerbSummary" placement="bottom" :show-after="300">
|
||||
<div class="create-order-rx-card__herbs">{{ createOrderHerbSummary }}</div>
|
||||
</el-tooltip>
|
||||
</el-tooltip> -->
|
||||
</div>
|
||||
|
||||
<el-steps
|
||||
@@ -1118,6 +1149,69 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 从处方库导入(与诊间 tcm-prescription 一致,按当前处方开方医师 creator_id 筛选) -->
|
||||
<el-dialog
|
||||
v-model="showLibraryDialog"
|
||||
title="从处方库导入"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="mb-4">
|
||||
<el-input
|
||||
v-model="librarySearchName"
|
||||
placeholder="请输入处方名称搜索"
|
||||
clearable
|
||||
@keyup.enter="searchLibrary"
|
||||
@clear="searchLibrary"
|
||||
>
|
||||
<template #append>
|
||||
<el-button :icon="Search" @click="searchLibrary" />
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="libraryLoading"
|
||||
:data="libraryList"
|
||||
height="400"
|
||||
style="cursor: pointer"
|
||||
@row-click="handleSelectLibraryRow"
|
||||
>
|
||||
<el-table-column label="处方名称" prop="prescription_name" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="药材数量" width="100">
|
||||
<template #default="{ row }">
|
||||
{{ row.herbs?.length || 0 }}味
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="药材明细" min-width="300" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.herbs && row.herbs.length > 0">
|
||||
{{ row.herbs.map((h: any) => `${h.name} ${h.dosage}g`).join('、') }}
|
||||
</span>
|
||||
<span v-else class="text-gray-400">暂无药材</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建人" prop="creator_name" width="100" />
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click.stop="handleImportLibrary(row)">导入</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<el-pagination
|
||||
v-model:current-page="libraryPage"
|
||||
v-model:page-size="libraryPageSize"
|
||||
:total="libraryTotal"
|
||||
:page-sizes="[10, 15, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@current-change="loadLibraryList"
|
||||
@size-change="loadLibraryList"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="auditDialogVisible" title="处方审核" width="480px" destroy-on-close @closed="auditRemark = ''">
|
||||
<p class="text-sm text-gray-600 mb-3">
|
||||
通过:处方保持有效。驳回:将同时作废此处方(与诊间「驳回/作废处方」一致)。
|
||||
@@ -1153,7 +1247,8 @@ import {
|
||||
prescriptionDetail,
|
||||
prescriptionOrderCreate,
|
||||
prescriptionOrderPaidPayOrders,
|
||||
getDoctors
|
||||
getDoctors,
|
||||
prescriptionLibraryLists
|
||||
} from '@/api/tcm'
|
||||
import { searchPatients as searchPatientsAPI } from '@/api/order'
|
||||
import { getDictData } from '@/api/app'
|
||||
@@ -1163,6 +1258,7 @@ import useUserStore from '@/stores/modules/user'
|
||||
import feedback from '@/utils/feedback'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import DaterangePicker from '@/components/daterange-picker/index.vue'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { computed, onMounted, reactive, ref, watch, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
@@ -1783,6 +1879,10 @@ const editForm = reactive({
|
||||
audit_by_name: '',
|
||||
audit_remark: '',
|
||||
diagnosis_id: 0,
|
||||
/** 开方医师 admin id(处方库导入按此医生筛选) */
|
||||
creator_id: 0,
|
||||
/** 1=系统自动生成(如挂号完成) */
|
||||
is_system_auto: 0,
|
||||
/** 列表带入:业务订单「处方审核」驳回(消费者处方本身可能仍为已通过) */
|
||||
business_prescription_audit_rejected: 0,
|
||||
business_prescription_audit_remark: '',
|
||||
@@ -1938,7 +2038,7 @@ function resetParams() {
|
||||
dateQuickTab.value = 'all'
|
||||
formData.patient_name = ''
|
||||
formData.creator_ids = []
|
||||
formData.audit_filter = userCanAudit() ? 'pending' : 'all'
|
||||
formData.audit_filter = 'all'
|
||||
formData.start_time = ''
|
||||
formData.end_time = ''
|
||||
syncingDateTab = false
|
||||
@@ -2103,6 +2203,108 @@ async function loadRoleOptions() {
|
||||
}
|
||||
}
|
||||
|
||||
// —— 处方库导入(与 components/tcm-prescription 一致,按 creator_id 拉取该医师库内处方)——
|
||||
const showLibraryDialog = ref(false)
|
||||
const libraryLoading = ref(false)
|
||||
const libraryList = ref<any[]>([])
|
||||
const librarySearchName = ref('')
|
||||
const libraryPage = ref(1)
|
||||
const libraryPageSize = ref(15)
|
||||
const libraryTotal = ref(0)
|
||||
|
||||
/** 当前用于检索处方库的开方医师 ID:编辑用处方 creator_id,新增用当前登录账号 */
|
||||
const libraryDoctorId = computed(() => {
|
||||
const cid = Number(editForm.creator_id)
|
||||
if (cid > 0) return cid
|
||||
const uid = Number(userStore.userInfo?.id)
|
||||
return uid > 0 ? uid : 0
|
||||
})
|
||||
|
||||
function normalizeHerbNameLocal(s: string) {
|
||||
return String(s || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
function findDuplicateHerbNamesLocal(): string[] {
|
||||
const map = new Map<string, number>()
|
||||
for (const h of editForm.herbs) {
|
||||
const key = normalizeHerbNameLocal(h?.name)
|
||||
if (!key) continue
|
||||
map.set(key, (map.get(key) ?? 0) + 1)
|
||||
}
|
||||
return [...map.entries()]
|
||||
.filter(([, c]) => c > 1)
|
||||
.map(([name]) => name)
|
||||
}
|
||||
|
||||
const loadLibraryList = async () => {
|
||||
const doctorId = libraryDoctorId.value
|
||||
if (!doctorId) {
|
||||
feedback.msgWarning('无法确定开方医师,无法加载处方库')
|
||||
libraryList.value = []
|
||||
libraryTotal.value = 0
|
||||
return
|
||||
}
|
||||
libraryLoading.value = true
|
||||
try {
|
||||
const res: any = await prescriptionLibraryLists({
|
||||
page_no: libraryPage.value,
|
||||
page_size: libraryPageSize.value,
|
||||
prescription_name: librarySearchName.value,
|
||||
is_public: '',
|
||||
creator_id: doctorId
|
||||
})
|
||||
libraryList.value = res?.lists || []
|
||||
libraryTotal.value = res?.count || 0
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
feedback.msgError('加载处方库失败')
|
||||
} finally {
|
||||
libraryLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const searchLibrary = () => {
|
||||
libraryPage.value = 1
|
||||
loadLibraryList()
|
||||
}
|
||||
|
||||
const openLibraryDialog = () => {
|
||||
const doctorId = libraryDoctorId.value
|
||||
if (!doctorId) {
|
||||
feedback.msgWarning('无法确定开方医师,无法从处方库导入')
|
||||
return
|
||||
}
|
||||
showLibraryDialog.value = true
|
||||
}
|
||||
|
||||
const handleSelectLibraryRow = (row: any) => {
|
||||
handleImportLibrary(row)
|
||||
}
|
||||
|
||||
const handleImportLibrary = (row: any) => {
|
||||
if (!row.herbs || row.herbs.length === 0) {
|
||||
feedback.msgWarning('该处方没有药材信息')
|
||||
return
|
||||
}
|
||||
const imported = JSON.parse(JSON.stringify(row.herbs)) as Array<{ name: string; dosage: number }>
|
||||
editForm.herbs = imported
|
||||
if (findDuplicateHerbNamesLocal().length) {
|
||||
feedback.msgWarning('导入的处方中存在重复药名,请合并剂量或删除多余行')
|
||||
}
|
||||
feedback.msgSuccess(`已导入处方「${row.prescription_name}」,共${imported.length}味药材`)
|
||||
showLibraryDialog.value = false
|
||||
}
|
||||
|
||||
watch(showLibraryDialog, (open) => {
|
||||
if (open) {
|
||||
librarySearchName.value = ''
|
||||
libraryPage.value = 1
|
||||
loadLibraryList()
|
||||
}
|
||||
})
|
||||
|
||||
// 添加药材
|
||||
const addHerb = () => {
|
||||
editForm.herbs.push({
|
||||
@@ -2147,6 +2349,8 @@ const resetForm = () => {
|
||||
editForm.audit_by_name = ''
|
||||
editForm.audit_remark = ''
|
||||
editForm.diagnosis_id = 0
|
||||
editForm.creator_id = Number(userStore.userInfo?.id) || 0
|
||||
editForm.is_system_auto = 0
|
||||
editForm.business_prescription_audit_rejected = 0
|
||||
editForm.business_prescription_audit_remark = ''
|
||||
}
|
||||
@@ -2249,6 +2453,8 @@ const handleEdit = (row: any) => {
|
||||
editForm.audit_by_name = row.audit_by_name || ''
|
||||
editForm.audit_remark = row.audit_remark || ''
|
||||
editForm.diagnosis_id = row.diagnosis_id ?? 0
|
||||
editForm.creator_id = Number(row.creator_id) > 0 ? Number(row.creator_id) : 0
|
||||
editForm.is_system_auto = Number(row.is_system_auto) === 1 ? 1 : 0
|
||||
editForm.business_prescription_audit_rejected = Number(row.business_prescription_audit_rejected) === 1 ? 1 : 0
|
||||
editForm.business_prescription_audit_remark = String(row.business_prescription_audit_remark || '')
|
||||
|
||||
@@ -2336,9 +2542,6 @@ onMounted(async () => {
|
||||
} catch {
|
||||
/* 已登录页仍可无用户信息,保持默认筛选 */
|
||||
}
|
||||
if (userCanAudit()) {
|
||||
formData.audit_filter = 'pending'
|
||||
}
|
||||
getLists()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -42,6 +42,22 @@
|
||||
<span class="font-medium">{{ tab.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<span class="text-gray-500 text-sm font-medium w-20 shrink-0">发货状态</span>
|
||||
<div
|
||||
v-for="tab in fulfillmentStatusTabs"
|
||||
:key="'fs-' + String(tab.value)"
|
||||
:class="[
|
||||
'px-4 py-1.5 rounded-lg cursor-pointer transition-all duration-200 select-none text-sm',
|
||||
queryParams.fulfillment_status === tab.value
|
||||
? 'bg-primary text-white shadow-md'
|
||||
: 'bg-gray-50 text-gray-600 hover:bg-gray-100'
|
||||
]"
|
||||
@click="handleFulfillmentStatusTabClick(tab.value)"
|
||||
>
|
||||
<span class="font-medium">{{ tab.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@@ -63,6 +79,46 @@
|
||||
@keyup.enter="resetPage"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[220px]" label="患者">
|
||||
<el-input
|
||||
v-model="queryParams.patient_keyword"
|
||||
placeholder="姓名 / 手机号"
|
||||
clearable
|
||||
@keyup.enter="resetPage"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[200px]" label="医生">
|
||||
<el-select
|
||||
v-model="queryParams.doctor_id"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="开方医生"
|
||||
class="!w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="d in doctorOptions"
|
||||
:key="d.id"
|
||||
:label="d.name"
|
||||
:value="d.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[200px]" label="医助">
|
||||
<el-select
|
||||
v-model="queryParams.assistant_id"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="诊单医助"
|
||||
class="!w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="a in assistantOptions"
|
||||
:key="a.id"
|
||||
:label="a.name"
|
||||
:value="a.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="resetPage">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
@@ -469,12 +525,29 @@
|
||||
<template v-else>—</template>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="服用方式">
|
||||
<template v-if="detailPrescription.usage_days || detailPrescription.times_per_day">
|
||||
<span v-if="detailPrescription.usage_days">服用{{ detailPrescription.usage_days }}天</span>
|
||||
<span v-if="detailPrescription.usage_days && detailPrescription.times_per_day">,</span>
|
||||
<span v-if="detailPrescription.times_per_day">每天{{ detailPrescription.times_per_day }}次</span>
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
<div class="flex flex-col gap-1.5 text-sm leading-relaxed">
|
||||
<div>
|
||||
<span class="text-gray-500">每天次数:</span>
|
||||
{{ detailPrescription.times_per_day ? detailPrescription.times_per_day + ' 次' : '—' }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-500">处方开立:</span>
|
||||
{{
|
||||
detailPrescription.usage_days != null && detailPrescription.usage_days !== ''
|
||||
? detailPrescription.usage_days + ' 天'
|
||||
: '—'
|
||||
}}
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-500">订单设置:</span>
|
||||
{{
|
||||
detailData.medication_days != null &&
|
||||
String(detailData.medication_days).trim() !== ''
|
||||
? detailData.medication_days + ' 天'
|
||||
: '—'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态" :span="2">
|
||||
<el-tag :type="Number(detailPrescription.void_status) === 1 ? 'danger' : 'success'" size="small">
|
||||
@@ -1468,7 +1541,7 @@
|
||||
<div class="cf-slip-footer">
|
||||
<div class="cf-slip-footer-left">
|
||||
<div>
|
||||
服用{{ prescriptionViewData.usage_days ?? prescriptionViewData.dose_count }}天, {{ prescriptionViewData.prescription_type || '浓缩水丸' }}
|
||||
服用{{ prescriptionViewData.medication_days ?? prescriptionViewData.usage_days ?? prescriptionViewData.dose_count }}天, {{ prescriptionViewData.prescription_type || '浓缩水丸' }}
|
||||
{{ prescriptionViewData.usage_instruction || '' }}
|
||||
</div>
|
||||
<div>
|
||||
@@ -1668,7 +1741,9 @@ import {
|
||||
prescriptionOrderLinkPayOrder,
|
||||
prescriptionOrderSubmitGancaoRecipel,
|
||||
prescriptionOrderPreviewGancaoRecipel,
|
||||
prescriptionDetail
|
||||
prescriptionDetail,
|
||||
getDoctors,
|
||||
getAssistants
|
||||
} from '@/api/tcm'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { jsPDF } from 'jspdf'
|
||||
@@ -1720,6 +1795,21 @@ const regionOptions = ref([])
|
||||
// 服务套餐选项
|
||||
const servicePackageOptions = ref<Array<{ name: string; value: string }>>([])
|
||||
|
||||
/** 筛选:开方医生、诊单医助(与列表接口 doctor_id / assistant_id 一致) */
|
||||
const doctorOptions = ref<Array<{ id: number; name: string }>>([])
|
||||
const assistantOptions = ref<Array<{ id: number; name: string }>>([])
|
||||
|
||||
async function loadDoctorAssistantOptions() {
|
||||
try {
|
||||
const [docs, ast] = await Promise.all([getDoctors(), getAssistants()])
|
||||
doctorOptions.value = Array.isArray(docs) ? docs : []
|
||||
assistantOptions.value = Array.isArray(ast) ? ast : []
|
||||
} catch {
|
||||
doctorOptions.value = []
|
||||
assistantOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 转换省市区数据格式
|
||||
const transformRegionData = (data: any[]) => {
|
||||
return data.map((province: any) => ({
|
||||
@@ -1780,19 +1870,18 @@ const loadServicePackageOptions = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 状态标签页配置
|
||||
const statusTabs = ref([
|
||||
{ label: '全部', value: '' as number | '', count: undefined },
|
||||
{ label: '待双审通过', value: 1, count: undefined },
|
||||
{ label: '履约中', value: 2, count: undefined },
|
||||
{ label: '已发货', value: 5, count: undefined },
|
||||
{ label: '已签收', value: 6, count: undefined },
|
||||
{ label: '已完成', value: 3, count: undefined },
|
||||
{ label: '已取消', value: 4, count: undefined }
|
||||
])
|
||||
/** 发货状态(与 fulfillment_status、列表列「履约状态」一致) */
|
||||
const fulfillmentStatusTabs = [
|
||||
{ label: '全部', value: '' as number | '' },
|
||||
{ label: '待双审通过', value: 1 },
|
||||
{ label: '待发货', value: 2 },
|
||||
{ label: '已发货', value: 5 },
|
||||
{ label: '已签收', value: 6 },
|
||||
{ label: '已完成', value: 3 },
|
||||
{ label: '已取消', value: 4 }
|
||||
]
|
||||
|
||||
// 处理状态标签点击
|
||||
const handleStatusTabClick = (value: number | '') => {
|
||||
function handleFulfillmentStatusTabClick(value: number | '') {
|
||||
queryParams.fulfillment_status = value
|
||||
resetPage()
|
||||
}
|
||||
@@ -1800,6 +1889,12 @@ const handleStatusTabClick = (value: number | '') => {
|
||||
const queryParams = reactive({
|
||||
order_no: '',
|
||||
prescription_id: '' as string | number,
|
||||
/** 诊单患者姓名/手机号(后台关联 diagnosis) */
|
||||
patient_keyword: '',
|
||||
/** 开方医生(后台:关联处方 creator_id) */
|
||||
doctor_id: '' as number | '',
|
||||
/** 诊单医助(后台:关联诊单 assistant_id) */
|
||||
assistant_id: '' as number | '',
|
||||
fulfillment_status: '' as number | '',
|
||||
prescription_audit_status: '' as number | '',
|
||||
payment_slip_audit_status: '' as number | ''
|
||||
@@ -1843,7 +1938,18 @@ async function fetchLists(params: Record<string, unknown>) {
|
||||
if (p.payment_slip_audit_status === '' || p.payment_slip_audit_status === undefined) {
|
||||
delete p.payment_slip_audit_status
|
||||
}
|
||||
if (p.doctor_id === '' || p.doctor_id === undefined || p.doctor_id === null) {
|
||||
delete p.doctor_id
|
||||
} else {
|
||||
p.doctor_id = Number(p.doctor_id)
|
||||
}
|
||||
if (p.assistant_id === '' || p.assistant_id === undefined || p.assistant_id === null) {
|
||||
delete p.assistant_id
|
||||
} else {
|
||||
p.assistant_id = Number(p.assistant_id)
|
||||
}
|
||||
if (!p.order_no) delete p.order_no
|
||||
if (!String(p.patient_keyword || '').trim()) delete p.patient_keyword
|
||||
return prescriptionOrderLists(p)
|
||||
}
|
||||
|
||||
@@ -1866,6 +1972,9 @@ watch(
|
||||
function handleReset() {
|
||||
queryParams.order_no = ''
|
||||
queryParams.prescription_id = ''
|
||||
queryParams.patient_keyword = ''
|
||||
queryParams.doctor_id = ''
|
||||
queryParams.assistant_id = ''
|
||||
queryParams.fulfillment_status = ''
|
||||
queryParams.prescription_audit_status = ''
|
||||
queryParams.payment_slip_audit_status = ''
|
||||
@@ -2842,6 +2951,7 @@ async function confirmSubmitGancaoRecipel(row: { id: number }) {
|
||||
onMounted(async () => {
|
||||
await loadRegionData()
|
||||
await loadServicePackageOptions()
|
||||
await loadDoctorAssistantOptions()
|
||||
getLists()
|
||||
})
|
||||
|
||||
@@ -3220,7 +3330,8 @@ const slipChuwanVisible = computed(() => {
|
||||
const slipPillGrams = computed(() => {
|
||||
const d = prescriptionViewData.value
|
||||
if (!d || (d.prescription_type || '浓缩水丸') !== '浓缩水丸') return null
|
||||
const days = Number(d.usage_days ?? d.dose_count)
|
||||
/** 用药疗程以业务订单 medication_days 为准,缺省时回退处方 usage_days / 剂数 */
|
||||
const days = Number(d.medication_days ?? d.usage_days ?? d.dose_count)
|
||||
const times = Number(d.times_per_day)
|
||||
const doseG = Number(d.dosage_amount)
|
||||
if (!Number.isFinite(days) || !Number.isFinite(times) || !Number.isFinite(doseG)) return null
|
||||
@@ -3266,7 +3377,17 @@ async function openPrescriptionView(row: any) {
|
||||
|
||||
try {
|
||||
const res: any = await prescriptionDetail({ id: row.prescription_id })
|
||||
prescriptionViewData.value = res?.data ?? res ?? null
|
||||
const base = res?.data ?? res ?? null
|
||||
if (base) {
|
||||
const md = row.medication_days
|
||||
const merged: Record<string, unknown> = { ...base }
|
||||
if (md != null && String(md).trim() !== '') {
|
||||
merged.medication_days = md
|
||||
}
|
||||
prescriptionViewData.value = merged
|
||||
} else {
|
||||
prescriptionViewData.value = null
|
||||
}
|
||||
} catch (e: any) {
|
||||
feedback.msgError(e?.message || '加载处方详情失败')
|
||||
prescriptionViewVisible.value = false
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">企业微信客户管理</span>
|
||||
<div class="flex gap-2">
|
||||
<el-button type="success" plain @click="openArrivalDrawer">
|
||||
<template #icon><DataLine /></template>
|
||||
今日进入明细
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="syncing" @click="syncCustomers()">
|
||||
<template #icon><Refresh /></template>
|
||||
立即同步
|
||||
@@ -34,9 +38,34 @@
|
||||
<div class="text-gray-500 text-sm mb-1">总客户数</div>
|
||||
<div class="text-2xl font-bold text-primary">{{ stats.total }}</div>
|
||||
</el-card>
|
||||
<el-card shadow="hover">
|
||||
<div class="text-gray-500 text-sm mb-1">今日新增</div>
|
||||
<div class="text-2xl font-bold text-success">{{ stats.today }}</div>
|
||||
<el-card shadow="hover" class="today-arrival-card cursor-pointer" @click="openArrivalDrawer">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-gray-500 text-sm">今日新增</span>
|
||||
<span v-if="arrival.recent_time" class="text-xs text-gray-400">
|
||||
最近 {{ formatHm(arrival.recent_time) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-end gap-2">
|
||||
<div class="text-2xl font-bold text-success leading-none">{{ stats.today }}</div>
|
||||
<div class="text-xs text-gray-400 pb-1">人</div>
|
||||
</div>
|
||||
<!-- 24 小时分布迷你柱:高度相对今日峰值等比 -->
|
||||
<div class="hourly-bars mt-2" :title="hourlyTooltip">
|
||||
<div
|
||||
v-for="(c, h) in arrival.hourly"
|
||||
:key="h"
|
||||
class="hourly-bar"
|
||||
:class="{
|
||||
'is-empty': c === 0,
|
||||
'is-current': h === currentHour
|
||||
}"
|
||||
:style="{ height: barHeight(c) }"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-2 pt-2 border-t border-gray-100">
|
||||
<div class="text-gray-500 text-xs mb-0.5">今日跟进人合计</div>
|
||||
<div class="text-lg font-semibold text-gray-800">{{ stats.today_follow_staff }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card shadow="hover">
|
||||
<div class="text-gray-500 text-sm mb-1">最后同步</div>
|
||||
@@ -167,6 +196,103 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 今日进入明细(事件流水零误差口径) -->
|
||||
<el-drawer
|
||||
v-model="showArrival"
|
||||
title="今日进入明细"
|
||||
direction="rtl"
|
||||
size="520px"
|
||||
:destroy-on-close="false"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between w-full pr-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-base font-semibold">今日进入明细</span>
|
||||
<el-tag type="success" effect="plain">共 {{ arrivalList.total }} 人</el-tag>
|
||||
</div>
|
||||
<el-button link type="primary" :loading="arrivalListLoading" @click="loadArrival(true)">
|
||||
<template #icon><Refresh /></template>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="px-1">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
class="mb-3"
|
||||
show-icon
|
||||
>
|
||||
<template #title>
|
||||
<span class="text-xs">
|
||||
一次推送 = 一个人进来;数据来源为企微回调流水表(INSERT IGNORE 幂等),与客户同步解耦,数字零误差。
|
||||
</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<div v-if="arrival.by_state?.length" class="mb-3">
|
||||
<div class="text-xs text-gray-500 mb-1">今日推广渠道 Top</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<el-tag
|
||||
v-for="it in arrival.by_state"
|
||||
:key="it.state || '_'"
|
||||
size="small"
|
||||
effect="light"
|
||||
type="success"
|
||||
>
|
||||
{{ it.state || '(无 state)' }} · {{ it.count }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-timeline v-if="arrivalList.lists.length" class="mt-2">
|
||||
<el-timeline-item
|
||||
v-for="row in arrivalList.lists"
|
||||
:key="row.id"
|
||||
:timestamp="formatHms(row.event_time)"
|
||||
placement="top"
|
||||
type="success"
|
||||
:hollow="false"
|
||||
>
|
||||
<div class="flex items-start gap-2">
|
||||
<el-avatar
|
||||
:size="32"
|
||||
:src="row.customer_avatar || undefined"
|
||||
>
|
||||
{{ (row.customer_name || '?').slice(0, 1) }}
|
||||
</el-avatar>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium truncate">
|
||||
{{ row.customer_name || row.external_userid || '(未知客户)' }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-0.5">
|
||||
<span>接待:{{ row.admin_name || row.user_id || '—' }}</span>
|
||||
<span v-if="row.state" class="ml-2">
|
||||
渠道:<el-tag size="small" type="info" effect="plain">{{ row.state }}</el-tag>
|
||||
</span>
|
||||
<span v-if="row.welcome_code" class="ml-2 text-emerald-500">欢迎语</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
<el-empty v-else description="今天还没有人进入" :image-size="80" />
|
||||
|
||||
<div
|
||||
v-if="arrivalList.total > arrivalList.lists.length"
|
||||
class="mt-3 text-center"
|
||||
>
|
||||
<el-button
|
||||
:loading="arrivalListLoading"
|
||||
size="small"
|
||||
@click="loadMoreArrival"
|
||||
>
|
||||
加载更多(还剩 {{ arrivalList.total - arrivalList.lists.length }})
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 客户详情对话框 -->
|
||||
<el-dialog
|
||||
v-model="showDetail"
|
||||
@@ -217,7 +343,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { Refresh, Setting } from '@element-plus/icons-vue'
|
||||
import { Refresh, Setting, DataLine } from '@element-plus/icons-vue'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import feedback from '@/utils/feedback'
|
||||
import {
|
||||
@@ -225,7 +351,9 @@ import {
|
||||
qywxCustomerSync,
|
||||
qywxCustomerStats,
|
||||
qywxSyncSettingsGet,
|
||||
qywxSyncSettingsSave
|
||||
qywxSyncSettingsSave,
|
||||
qywxCustomerTodayArrival,
|
||||
qywxCustomerTodayArrivalList
|
||||
} from '@/api/qywx'
|
||||
|
||||
const syncing = ref(false)
|
||||
@@ -236,6 +364,7 @@ const currentCustomer = ref<any>(null)
|
||||
const stats = reactive({
|
||||
total: 0,
|
||||
today: 0,
|
||||
today_follow_staff: 0,
|
||||
lastSync: '',
|
||||
syncStatus: 'idle',
|
||||
syncStatusText: '未同步'
|
||||
@@ -264,6 +393,124 @@ const queryParams = reactive({
|
||||
follow_user: ''
|
||||
})
|
||||
|
||||
// ── 今日进入分布 / 明细(事件流水零误差口径) ──────────────────────────────
|
||||
interface ArrivalStats {
|
||||
total: number
|
||||
recent_time: number
|
||||
hourly: number[]
|
||||
by_state: { state: string; count: number }[]
|
||||
}
|
||||
interface ArrivalItem {
|
||||
id: number
|
||||
event_time: number
|
||||
user_id: string
|
||||
admin_name: string
|
||||
external_userid: string
|
||||
customer_name: string
|
||||
customer_avatar: string
|
||||
state: string
|
||||
welcome_code: number
|
||||
}
|
||||
|
||||
const arrival = reactive<ArrivalStats>({
|
||||
total: 0,
|
||||
recent_time: 0,
|
||||
hourly: new Array(24).fill(0),
|
||||
by_state: []
|
||||
})
|
||||
const showArrival = ref(false)
|
||||
const arrivalListLoading = ref(false)
|
||||
const arrivalList = reactive<{ total: number; page_no: number; page_size: number; lists: ArrivalItem[] }>({
|
||||
total: 0,
|
||||
page_no: 1,
|
||||
page_size: 20,
|
||||
lists: []
|
||||
})
|
||||
const currentHour = new Date().getHours()
|
||||
const hourlyMax = computed(() => Math.max(1, ...arrival.hourly))
|
||||
function barHeight(c: number) {
|
||||
// 柱图容器高度 32px;0 值保留一条 2px 的底线让用户知道"这个小时有格子"
|
||||
if (c <= 0) return '2px'
|
||||
const ratio = c / hourlyMax.value
|
||||
return Math.max(4, Math.round(ratio * 32)) + 'px'
|
||||
}
|
||||
const hourlyTooltip = computed(() => {
|
||||
const parts: string[] = []
|
||||
arrival.hourly.forEach((c, h) => {
|
||||
if (c > 0) parts.push(`${String(h).padStart(2, '0')}:00 · ${c}`)
|
||||
})
|
||||
return parts.length ? parts.join(' ') : '今天还没有人进入'
|
||||
})
|
||||
|
||||
async function loadArrival(refreshList = false) {
|
||||
try {
|
||||
const res: any = await qywxCustomerTodayArrival()
|
||||
if (res) {
|
||||
arrival.total = Number(res.total ?? 0)
|
||||
arrival.recent_time = Number(res.recent_time ?? 0)
|
||||
arrival.hourly = Array.isArray(res.hourly) && res.hourly.length === 24
|
||||
? res.hourly.map((n: any) => Number(n) || 0)
|
||||
: new Array(24).fill(0)
|
||||
arrival.by_state = Array.isArray(res.by_state) ? res.by_state : []
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载今日进入分布失败', e)
|
||||
}
|
||||
if (refreshList && showArrival.value) {
|
||||
arrivalList.page_no = 1
|
||||
arrivalList.lists = []
|
||||
await fetchArrivalPage()
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchArrivalPage() {
|
||||
arrivalListLoading.value = true
|
||||
try {
|
||||
const res: any = await qywxCustomerTodayArrivalList({
|
||||
page_no: arrivalList.page_no,
|
||||
page_size: arrivalList.page_size
|
||||
})
|
||||
if (res) {
|
||||
arrivalList.total = Number(res.total ?? 0)
|
||||
const rows: ArrivalItem[] = Array.isArray(res.lists) ? res.lists : []
|
||||
if (arrivalList.page_no === 1) {
|
||||
arrivalList.lists = rows
|
||||
} else {
|
||||
arrivalList.lists.push(...rows)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载今日进入明细失败', e)
|
||||
} finally {
|
||||
arrivalListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openArrivalDrawer() {
|
||||
showArrival.value = true
|
||||
arrivalList.page_no = 1
|
||||
arrivalList.lists = []
|
||||
// 分布和明细一起刷新,确保抽屉里看到的数字和外面卡片一致
|
||||
await Promise.all([loadArrival(false), fetchArrivalPage()])
|
||||
}
|
||||
|
||||
async function loadMoreArrival() {
|
||||
if (arrivalList.lists.length >= arrivalList.total) return
|
||||
arrivalList.page_no += 1
|
||||
await fetchArrivalPage()
|
||||
}
|
||||
|
||||
function formatHm(ts: number) {
|
||||
if (!ts || ts <= 0) return ''
|
||||
const d = new Date(ts < 1e12 ? ts * 1000 : ts)
|
||||
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
|
||||
}
|
||||
function formatHms(ts: number) {
|
||||
if (!ts || ts <= 0) return '—'
|
||||
const d = new Date(ts < 1e12 ? ts * 1000 : ts)
|
||||
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
let syncTimer: number | null = null
|
||||
/** 后台同步完成后轮询统计,直到非「同步中」 */
|
||||
let pollSyncTimer: number | null = null
|
||||
@@ -453,15 +700,34 @@ function formatTime(timestamp: number | string | null | undefined) {
|
||||
return new Date(ms).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
/** 今日进入分布 30s 轮询:回调一到,事件流水立刻能反映在卡片上 */
|
||||
let arrivalPollTimer: number | null = null
|
||||
function startArrivalPolling() {
|
||||
if (arrivalPollTimer !== null) return
|
||||
arrivalPollTimer = window.setInterval(() => {
|
||||
loadStats()
|
||||
loadArrival(showArrival.value)
|
||||
}, 30000)
|
||||
}
|
||||
function stopArrivalPolling() {
|
||||
if (arrivalPollTimer !== null) {
|
||||
clearInterval(arrivalPollTimer)
|
||||
arrivalPollTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLists()
|
||||
loadStats()
|
||||
loadSyncSettings()
|
||||
loadArrival(false)
|
||||
startArrivalPolling()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
stopSyncTimer()
|
||||
stopPollSync()
|
||||
stopArrivalPolling()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -470,4 +736,37 @@ onUnmounted(() => {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.today-arrival-card {
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.hourly-bars {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(24, 1fr);
|
||||
align-items: end;
|
||||
gap: 2px;
|
||||
height: 32px;
|
||||
|
||||
.hourly-bar {
|
||||
background: var(--el-color-success);
|
||||
border-radius: 2px 2px 0 0;
|
||||
min-height: 2px;
|
||||
transition: height 0.3s ease;
|
||||
|
||||
&.is-empty {
|
||||
background: var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
&.is-current {
|
||||
outline: 1px solid var(--el-color-success-dark-2);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -22,6 +22,23 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item class="w-[280px]" label="医助">
|
||||
<el-select
|
||||
v-model="queryParams.assistant_id"
|
||||
placeholder="全部医助"
|
||||
clearable
|
||||
filterable
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in assistantOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="Number(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item class="w-[280px]" label="订单类型">
|
||||
<el-select v-model="queryParams.order_type" placeholder="请选择" clearable>
|
||||
<el-option label="挂号费" :value="1" />
|
||||
@@ -523,6 +540,7 @@
|
||||
<script setup lang="ts" name="orderList">
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import { orderLists, orderDetail, orderPay, orderCancel, orderDelete, orderRefund, orderExport, orderCreate, orderCreateForWechatWork, orderEdit, orderTodayRevenue, syncWechatWorkBills, syncWechatWorkBillsDebug, searchPatients as searchPatientsAPI, alipayPay, wechatPay } from '@/api/order'
|
||||
import { getAssistants } from '@/api/tcm'
|
||||
import { generateOrderQrcode } from '@/api/tcm'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
import feedback from '@/utils/feedback'
|
||||
@@ -530,6 +548,7 @@ import feedback from '@/utils/feedback'
|
||||
const queryParams = reactive({
|
||||
order_no: '',
|
||||
patient_keyword: '',
|
||||
assistant_id: undefined as number | undefined,
|
||||
order_type: '',
|
||||
status: '',
|
||||
patient_association: '' as '' | 'pending' | 'associated',
|
||||
@@ -537,6 +556,8 @@ const queryParams = reactive({
|
||||
create_time_end: ''
|
||||
})
|
||||
|
||||
const assistantOptions = ref<Array<{ id: number; name: string }>>([])
|
||||
|
||||
const patientAssociationTab = ref<'' | 'pending' | 'associated'>('')
|
||||
const todayRevenue = ref({ amount: '0.00', count: 0 })
|
||||
|
||||
@@ -1049,7 +1070,17 @@ const handleOrderQrcodeRefresh = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadAssistantOptions = async () => {
|
||||
try {
|
||||
const list = await getAssistants()
|
||||
assistantOptions.value = Array.isArray(list) ? list : []
|
||||
} catch {
|
||||
assistantOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadAssistantOptions()
|
||||
getLists()
|
||||
fetchTodayRevenue()
|
||||
})
|
||||
|
||||
@@ -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