Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58cc59cf8a | ||
|
|
796441b407 | ||
|
|
f6688a740d | ||
|
|
a347e0642c | ||
|
|
13adbac3fd | ||
|
|
333a18c37e | ||
|
|
e3a91a91f7 | ||
|
|
5ca5f06255 | ||
|
|
e01f3e7072 | ||
|
|
ed78803015 | ||
|
|
546138d60e | ||
|
|
fdddfdb3dd | ||
|
|
f5a89b4310 | ||
|
|
a650747fd3 | ||
|
|
46b62229a6 | ||
|
|
0f33f2ad28 | ||
|
|
a677b69e97 | ||
|
|
6432e9990c | ||
|
|
4d6125268a | ||
|
|
7f93cf5480 | ||
|
|
f8f1953a2a | ||
|
|
bde396189a |
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
@@ -61,6 +61,14 @@ export function tcmDiagnosisDetail(params: any) {
|
||||
return request.get({ url: '/tcm.diagnosis/detail', params })
|
||||
}
|
||||
|
||||
/** 设置复诊接诊率统计起始偏移(统计诊次=实单序号+偏移;1=二诊起,2=三诊起) */
|
||||
export function tcmDiagnosisSetRevisitSlotStartOffset(params: {
|
||||
id: number
|
||||
revisit_slot_start_offset: number
|
||||
}) {
|
||||
return request.post({ url: '/tcm.diagnosis/setRevisitSlotStartOffset', params })
|
||||
}
|
||||
|
||||
/** 诊单挂号 / 取消挂号 操作日志 */
|
||||
export function tcmDiagnosisGuahaoLogList(params: { id: number }) {
|
||||
return request.get({ url: '/tcm.diagnosis/guahaoLogList', params })
|
||||
@@ -425,6 +433,17 @@ export function prescriptionOrderPatchPrescriptionPatient(params: {
|
||||
return request.post({ url: '/tcm.prescriptionOrder/patchPrescriptionPatient', params })
|
||||
}
|
||||
|
||||
export function prescriptionOrderPatchPrescriptionUsage(params: {
|
||||
id: number
|
||||
times_per_day: number
|
||||
usage_days: number
|
||||
medication_days: number
|
||||
aux_times_per_day?: number
|
||||
aux_usage_days?: number
|
||||
}) {
|
||||
return request.post({ url: '/tcm.prescriptionOrder/patchPrescriptionUsage', params })
|
||||
}
|
||||
|
||||
export function prescriptionOrderAuditPrescription(params: {
|
||||
id: number
|
||||
action: 'approve' | 'reject'
|
||||
|
||||
+212
-5
@@ -327,7 +327,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="服用方式">
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="flex items-center gap-1">
|
||||
<span>服用方式</span>
|
||||
<el-button
|
||||
v-if="
|
||||
!readonly &&
|
||||
detailData.prescription_id &&
|
||||
detailPrescription &&
|
||||
!String(detailData.prescription_detail_error || '').trim()
|
||||
"
|
||||
v-perms="['tcm.prescriptionOrder/patchPrescriptionUsage']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
@click="openPatchUsageDialog"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col gap-1.5 text-sm leading-relaxed">
|
||||
<template v-if="detailHasAuxHerbs">
|
||||
<div>
|
||||
@@ -885,12 +905,98 @@
|
||||
<el-button type="primary" :loading="addLogSaving" @click="submitAddLog">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 修改服用参数:主方 / 辅方 / 订单设置 -->
|
||||
<el-dialog
|
||||
v-model="patchUsageVisible"
|
||||
title="修改服用参数"
|
||||
width="480px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@closed="resetPatchUsageForm"
|
||||
>
|
||||
<el-form
|
||||
ref="patchUsageFormRef"
|
||||
:model="patchUsageForm"
|
||||
:rules="patchUsageRules"
|
||||
label-width="108px"
|
||||
>
|
||||
<div v-if="detailHasAuxHerbs" class="text-xs font-medium text-gray-500 mb-3">主方</div>
|
||||
<el-form-item label="每天次数" prop="times_per_day">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.times_per_day"
|
||||
:min="1"
|
||||
:max="6"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="处方开立" prop="usage_days">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.usage_days"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<span class="text-gray-500 shrink-0">天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<template v-if="detailHasAuxHerbs">
|
||||
<div class="text-xs font-medium text-gray-500 mb-3 mt-2 pt-2 border-t border-gray-100">辅方</div>
|
||||
<el-form-item label="每天次数" prop="aux_times_per_day">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.aux_times_per_day"
|
||||
:min="1"
|
||||
:max="6"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="处方开立" prop="aux_usage_days">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.aux_usage_days"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<span class="text-gray-500 shrink-0">天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<div class="text-xs font-medium text-gray-500 mb-3 mt-2 pt-2 border-t border-gray-100">订单设置</div>
|
||||
<el-form-item label="服用天数" prop="medication_days">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.medication_days"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<span class="text-gray-500 shrink-0">天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="patchUsageVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="patchUsageSaving" @click="submitPatchUsage">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="PrescriptionOrderDetailDrawer">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { Refresh, Loading, Search, Van } from '@element-plus/icons-vue'
|
||||
import {
|
||||
@@ -899,7 +1005,8 @@ import {
|
||||
prescriptionOrderAddLog,
|
||||
prescriptionOrderLogisticsTrace,
|
||||
prescriptionOrderLogisticsJdUpdate,
|
||||
prescriptionOrderPaidPayOrders
|
||||
prescriptionOrderPaidPayOrders,
|
||||
prescriptionOrderPatchPrescriptionUsage
|
||||
} from '@/api/tcm'
|
||||
import { getDictData } from '@/api/app'
|
||||
import feedback from '@/utils/feedback'
|
||||
@@ -1183,11 +1290,15 @@ const detailFullAddress = computed(() => {
|
||||
const servicePackageOptions = ref<ServicePackageOption[]>([])
|
||||
|
||||
async function loadServicePackageOptions() {
|
||||
if (servicePackageOptions.value.length > 0) return
|
||||
try {
|
||||
const data: any = await getDictData({ type: 'server_order' })
|
||||
servicePackageOptions.value = normalizeServicePackageOptions(data?.server_order)
|
||||
const opts = normalizeServicePackageOptions(data?.server_order)
|
||||
if (opts.length > 0) {
|
||||
servicePackageOptions.value = opts
|
||||
}
|
||||
} catch {
|
||||
servicePackageOptions.value = []
|
||||
/* 请求被同参数请求取消或失败时保留现值,open() 时会重试 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1257,6 +1368,100 @@ const addLogRules: FormRules = {
|
||||
summary: [{ required: true, message: '请填写日志内容', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const patchUsageVisible = ref(false)
|
||||
const patchUsageSaving = ref(false)
|
||||
const patchUsageFormRef = ref<FormInstance>()
|
||||
const patchUsageForm = reactive({
|
||||
times_per_day: 3 as number | undefined,
|
||||
usage_days: 7 as number | undefined,
|
||||
aux_times_per_day: 3 as number | undefined,
|
||||
aux_usage_days: 7 as number | undefined,
|
||||
medication_days: undefined as number | undefined
|
||||
})
|
||||
const patchUsageRules = computed<FormRules>(() => {
|
||||
const rules: FormRules = {
|
||||
times_per_day: [{ required: true, message: '请填写主方每天次数', trigger: 'change' }],
|
||||
usage_days: [{ required: true, message: '请填写主方开立天数', trigger: 'change' }],
|
||||
medication_days: [{ required: true, message: '请填写订单服用天数', trigger: 'change' }]
|
||||
}
|
||||
if (detailHasAuxHerbs.value) {
|
||||
rules.aux_times_per_day = [{ required: true, message: '请填写辅方每天次数', trigger: 'change' }]
|
||||
rules.aux_usage_days = [{ required: true, message: '请填写辅方开立天数', trigger: 'change' }]
|
||||
}
|
||||
return rules
|
||||
})
|
||||
|
||||
function openPatchUsageDialog() {
|
||||
const rx = detailPrescription.value
|
||||
const ord = detailData.value
|
||||
if (!rx || !ord?.id || !ord.prescription_id) {
|
||||
feedback.msgWarning('无处方数据')
|
||||
return
|
||||
}
|
||||
const aux = detailAuxUsage.value
|
||||
patchUsageForm.times_per_day =
|
||||
Number(rx.times_per_day) > 0 ? Number(rx.times_per_day) : 3
|
||||
patchUsageForm.usage_days =
|
||||
Number(rx.usage_days) > 0 ? Number(rx.usage_days) : 7
|
||||
patchUsageForm.aux_times_per_day =
|
||||
aux && Number(aux.times_per_day) > 0 ? Number(aux.times_per_day) : 3
|
||||
patchUsageForm.aux_usage_days =
|
||||
aux && Number(aux.usage_days) > 0 ? Number(aux.usage_days) : 7
|
||||
const md = Number(ord.medication_days)
|
||||
patchUsageForm.medication_days = md > 0 ? md : Number(rx.usage_days) > 0 ? Number(rx.usage_days) : 7
|
||||
patchUsageVisible.value = true
|
||||
nextTick(() => patchUsageFormRef.value?.clearValidate())
|
||||
}
|
||||
|
||||
function resetPatchUsageForm() {
|
||||
patchUsageForm.times_per_day = 3
|
||||
patchUsageForm.usage_days = 7
|
||||
patchUsageForm.aux_times_per_day = 3
|
||||
patchUsageForm.aux_usage_days = 7
|
||||
patchUsageForm.medication_days = undefined
|
||||
}
|
||||
|
||||
async function submitPatchUsage() {
|
||||
const form = patchUsageFormRef.value
|
||||
if (!form) return
|
||||
try {
|
||||
await form.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
const ordId = detailData.value?.id
|
||||
if (!ordId) return
|
||||
patchUsageSaving.value = true
|
||||
try {
|
||||
const payload: {
|
||||
id: number
|
||||
times_per_day: number
|
||||
usage_days: number
|
||||
medication_days: number
|
||||
aux_times_per_day?: number
|
||||
aux_usage_days?: number
|
||||
} = {
|
||||
id: ordId,
|
||||
times_per_day: Number(patchUsageForm.times_per_day),
|
||||
usage_days: Number(patchUsageForm.usage_days),
|
||||
medication_days: Number(patchUsageForm.medication_days)
|
||||
}
|
||||
if (detailHasAuxHerbs.value) {
|
||||
payload.aux_times_per_day = Number(patchUsageForm.aux_times_per_day)
|
||||
payload.aux_usage_days = Number(patchUsageForm.aux_usage_days)
|
||||
}
|
||||
await prescriptionOrderPatchPrescriptionUsage(payload)
|
||||
feedback.msgSuccess('保存成功')
|
||||
patchUsageVisible.value = false
|
||||
await refresh()
|
||||
emit('detail-changed')
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
patchUsageSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function resetAddLogForm() {
|
||||
addLogForm.summary = ''
|
||||
addLogForm.prescription_audit_status = ''
|
||||
@@ -1438,6 +1643,8 @@ async function updateJdLogistics() {
|
||||
|
||||
// ─── 打开 / 刷新 ───
|
||||
async function open(id: number) {
|
||||
// 页面级同参数字典请求会取消抽屉挂载时的那次(axios 去重取消),打开时兜底重试
|
||||
void loadServicePackageOptions()
|
||||
// 显式彻底清空缓存,防止前一次弹窗的数据残留
|
||||
detailData.value = null
|
||||
detailUnlinkedPayOrders.value = []
|
||||
|
||||
@@ -135,6 +135,7 @@ export function logActionText(act: string) {
|
||||
revoke_pay_audit: '撤回支付审核',
|
||||
gancao_submit: '甘草下单',
|
||||
patch_rx_patient: '处方患者信息',
|
||||
patch_rx_usage: '服用参数',
|
||||
update_amount: '修改订单金额',
|
||||
complete: '完成订单',
|
||||
refund: '退款',
|
||||
|
||||
@@ -298,7 +298,7 @@
|
||||
:fetch-fun="prescriptionOrderExport"
|
||||
:params="prescriptionOrderExportParams"
|
||||
:page-size="pager.size"
|
||||
export-hint="导出范围与上方筛选一致(履约状态、创建时间及其他条件均会生效)。含「自媒体渠道(挂号渠道来源)」:优先取该单关联处方登记的挂号;无则诊单下同患者挂号取 id 最大的一条(与前台挂号选择的记录一致);业绩侧栏带渠道筛选导出时与同页列表高亮挂号同源。「服务套餐」按字典 server_order 解析展示。「处方」导出主方/辅方药材明细;「主方/辅方服用方式、天数」与详情侧栏、处方笺同口径(天数优先取订单 medication_days,缺省回退处方 usage_days / 辅方 aux_usage)。「关联收款记录」与详情侧栏同源(已支付/已退款/待审核),每笔两行展示(摘要行+明细行),多笔空行分隔,单元格自动换行。「签收日期」与详情/业绩看板同源,仅读物流库(轨迹/签收时间):导出不再实时查快递100,速度只取决于数据库;签收时间由 `tcm:backfill-sign-time` 命令与物流自动更新定时任务落库,刚发货尚未同步的单子会暂时为空,待下次回填/定时任务刷新后显示。"
|
||||
export-hint="导出范围与上方筛选一致(履约状态、创建时间及其他条件均会生效)。含「自媒体渠道(挂号渠道来源)」:优先取该单关联处方登记的挂号;无则诊单下同患者挂号取 id 最大的一条(与前台挂号选择的记录一致);业绩侧栏带渠道筛选导出时与同页列表高亮挂号同源。「服务套餐」按字典 server_order 解析展示。「处方」导出主方/辅方药材明细;「主方/辅方服用方式、天数」与详情侧栏同口径(主方/辅方天数分别取处方 usage_days、辅方 aux_usage.usage_days;「天数」列为订单 medication_days)。「关联收款记录」与详情侧栏同源(已支付/已退款/待审核),每笔两行展示(摘要行+明细行),多笔空行分隔,单元格自动换行。「签收日期」与详情/业绩看板同源,仅读物流库(轨迹/签收时间):导出不再实时查快递100,速度只取决于数据库;签收时间由 `tcm:backfill-sign-time` 命令与物流自动更新定时任务落库,刚发货尚未同步的单子会暂时为空,待下次回填/定时任务刷新后显示。"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@@ -858,31 +858,94 @@
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用量">
|
||||
<template v-if="detailPrescription.dosage_amount">
|
||||
{{ detailPrescription.dosage_amount }}{{ detailPrescription.dosage_unit || 'g' }}
|
||||
<template v-if="detailPrescription.prescription_type === '浓缩水丸'">
|
||||
· {{ Number(detailPrescription.dosage_bag_count) > 0 ? Number(detailPrescription.dosage_bag_count) : 1 }}袋
|
||||
</template>
|
||||
<span v-if="detailPrescription.prescription_type === '饮片' && detailPrescription.need_decoction !== null" class="ml-2 text-gray-500">
|
||||
({{ detailPrescription.need_decoction ? '代煎' : '不代煎' }})
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
<div class="flex flex-col gap-1 text-sm leading-relaxed">
|
||||
<div>
|
||||
<span v-if="detailHasAuxHerbs" class="text-gray-500 mr-1">主方:</span>
|
||||
<template v-if="detailPrescription.dosage_amount">
|
||||
{{ detailPrescription.dosage_amount }}{{ detailPrescription.dosage_unit || 'g' }}
|
||||
<template v-if="detailPrescription.prescription_type === '浓缩水丸'">
|
||||
· {{ Number(detailPrescription.dosage_bag_count) > 0 ? Number(detailPrescription.dosage_bag_count) : 1 }}袋
|
||||
</template>
|
||||
<span v-if="detailPrescription.prescription_type === '饮片' && detailPrescription.need_decoction !== null" class="ml-2 text-gray-500">
|
||||
({{ detailPrescription.need_decoction ? '代煎' : '不代煎' }})
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
</div>
|
||||
<div v-if="detailHasAuxHerbs && detailAuxUsage">
|
||||
<span class="text-gray-500 mr-1">辅方:</span>
|
||||
<template v-if="detailAuxUsage.dosage_amount != null && detailAuxUsage.dosage_amount !== 0">
|
||||
{{ detailAuxUsage.dosage_amount }}{{ detailPrescription.dosage_unit || 'g' }}
|
||||
<template v-if="detailPrescription.prescription_type === '浓缩水丸'">
|
||||
· {{ Number(detailAuxUsage.dosage_bag_count) > 0 ? Number(detailAuxUsage.dosage_bag_count) : 1 }}袋
|
||||
</template>
|
||||
<span v-if="detailPrescription.prescription_type === '饮片'" class="ml-2 text-gray-500">
|
||||
({{ detailAuxUsage.need_decoction ? '代煎' : '不代煎' }})
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
</div>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="服用方式">
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="flex items-center gap-1 flex-wrap">
|
||||
<span>服用方式</span>
|
||||
<el-button
|
||||
v-if="
|
||||
detailData.prescription_id &&
|
||||
detailPrescription &&
|
||||
!String(detailData.prescription_detail_error || '').trim()
|
||||
"
|
||||
v-perms="['tcm.prescriptionOrder/patchPrescriptionUsage']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
@click="openPatchUsageDialog"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
</div>
|
||||
</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>
|
||||
<template v-if="detailHasAuxHerbs">
|
||||
<div>
|
||||
<span class="text-gray-500 mr-1">主方:</span>
|
||||
每天
|
||||
{{ detailPrescription.times_per_day ? detailPrescription.times_per_day + ' 次' : '— 次' }}
|
||||
· 处方开立
|
||||
{{
|
||||
detailPrescription.usage_days != null && detailPrescription.usage_days !== ''
|
||||
? detailPrescription.usage_days + ' 天'
|
||||
: '— 天'
|
||||
}}
|
||||
</div>
|
||||
<div v-if="detailAuxUsage">
|
||||
<span class="text-gray-500 mr-1">辅方:</span>
|
||||
每天
|
||||
{{ detailAuxUsage.times_per_day ? detailAuxUsage.times_per_day + ' 次' : '— 次' }}
|
||||
· 处方开立
|
||||
{{
|
||||
detailAuxUsage.usage_days != null && Number(detailAuxUsage.usage_days) > 0
|
||||
? detailAuxUsage.usage_days + ' 天'
|
||||
: '— 天'
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<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>
|
||||
</template>
|
||||
<div>
|
||||
<span class="text-gray-500">订单设置:</span>
|
||||
{{
|
||||
@@ -2152,6 +2215,93 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 修改服用参数:主方 / 辅方 / 订单设置 -->
|
||||
<el-dialog
|
||||
v-model="patchUsageVisible"
|
||||
title="修改服用参数"
|
||||
width="92%"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
class="po-h5-dialog"
|
||||
@closed="resetPatchUsageForm"
|
||||
>
|
||||
<el-form
|
||||
ref="patchUsageFormRef"
|
||||
:model="patchUsageForm"
|
||||
:rules="patchUsageRules"
|
||||
label-width="96px"
|
||||
>
|
||||
<div v-if="detailHasAuxHerbs" class="text-xs font-medium text-gray-500 mb-3">主方</div>
|
||||
<el-form-item label="每天次数" prop="times_per_day">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.times_per_day"
|
||||
:min="1"
|
||||
:max="6"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="处方开立" prop="usage_days">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.usage_days"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<span class="text-gray-500 shrink-0">天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<template v-if="detailHasAuxHerbs">
|
||||
<div class="text-xs font-medium text-gray-500 mb-3 mt-2 pt-2 border-t border-gray-100">辅方</div>
|
||||
<el-form-item label="每天次数" prop="aux_times_per_day">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.aux_times_per_day"
|
||||
:min="1"
|
||||
:max="6"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="处方开立" prop="aux_usage_days">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.aux_usage_days"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<span class="text-gray-500 shrink-0">天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<div class="text-xs font-medium text-gray-500 mb-3 mt-2 pt-2 border-t border-gray-100">订单设置</div>
|
||||
<el-form-item label="服用天数" prop="medication_days">
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
<el-input-number
|
||||
v-model="patchUsageForm.medication_days"
|
||||
:min="1"
|
||||
:max="999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<span class="text-gray-500 shrink-0">天</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="patchUsageVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="patchUsageSaving" @click="submitPatchUsage">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 处方详情查看(处方单样式) -->
|
||||
<el-drawer
|
||||
v-model="prescriptionViewVisible"
|
||||
@@ -2566,6 +2716,7 @@ import {
|
||||
prescriptionOrderRevokeRxAudit,
|
||||
prescriptionOrderRevokePayAudit,
|
||||
prescriptionOrderPatchPrescriptionPatient,
|
||||
prescriptionOrderPatchPrescriptionUsage,
|
||||
prescriptionOrderLinkPayOrder,
|
||||
prescriptionOrderRequestCompletion,
|
||||
prescriptionOrderSubmitGancaoRecipel,
|
||||
@@ -2580,7 +2731,9 @@ import {
|
||||
normalizeServicePackageOptions,
|
||||
parseServicePackageValues,
|
||||
mergeServicePackageSelectOptions,
|
||||
formatServicePackageLabels
|
||||
formatServicePackageLabels,
|
||||
normalizeSlipAuxUsageForm,
|
||||
prescriptionHasAuxFormula
|
||||
} from './components/prescription-order-utils'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { jsPDF } from 'jspdf'
|
||||
@@ -3618,6 +3771,16 @@ const detailLinkedAppointmentResolvedFromTag = computed(() => {
|
||||
|
||||
const detailRxHerbs = computed(() => normalizeSlipHerbs(detailPrescription.value?.herbs))
|
||||
|
||||
const detailHasAuxHerbs = computed(() => prescriptionHasAuxFormula(detailPrescription.value as any))
|
||||
|
||||
const detailAuxUsage = computed(() => {
|
||||
const rx = detailPrescription.value as any
|
||||
if (!rx || !prescriptionHasAuxFormula(rx)) return null
|
||||
const raw = rx.aux_usage
|
||||
if (raw == null || raw === '' || (Array.isArray(raw) && raw.length === 0)) return null
|
||||
return normalizeSlipAuxUsageForm(raw, rx.prescription_type || '浓缩水丸')
|
||||
})
|
||||
|
||||
/** false=无权限;true/缺省兼容旧接口(旧版未下发该字段时仍展示药材) */
|
||||
const detailHerbsVisible = computed(() => detailData.value?.prescription_detail_herbs_visible !== false)
|
||||
|
||||
@@ -3844,6 +4007,101 @@ const patchRxPatientRules: FormRules = {
|
||||
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const patchUsageVisible = ref(false)
|
||||
const patchUsageSaving = ref(false)
|
||||
const patchUsageFormRef = ref<FormInstance>()
|
||||
const patchUsageForm = reactive({
|
||||
times_per_day: 3 as number | undefined,
|
||||
usage_days: 7 as number | undefined,
|
||||
aux_times_per_day: 3 as number | undefined,
|
||||
aux_usage_days: 7 as number | undefined,
|
||||
medication_days: undefined as number | undefined
|
||||
})
|
||||
const patchUsageRules = computed<FormRules>(() => {
|
||||
const rules: FormRules = {
|
||||
times_per_day: [{ required: true, message: '请填写主方每天次数', trigger: 'change' }],
|
||||
usage_days: [{ required: true, message: '请填写主方开立天数', trigger: 'change' }],
|
||||
medication_days: [{ required: true, message: '请填写订单服用天数', trigger: 'change' }]
|
||||
}
|
||||
if (detailHasAuxHerbs.value) {
|
||||
rules.aux_times_per_day = [{ required: true, message: '请填写辅方每天次数', trigger: 'change' }]
|
||||
rules.aux_usage_days = [{ required: true, message: '请填写辅方开立天数', trigger: 'change' }]
|
||||
}
|
||||
return rules
|
||||
})
|
||||
|
||||
function openPatchUsageDialog() {
|
||||
const rx = detailPrescription.value
|
||||
const ord = detailData.value
|
||||
if (!rx || !ord?.id || !ord.prescription_id) {
|
||||
feedback.msgWarning('无处方数据')
|
||||
return
|
||||
}
|
||||
const aux = detailAuxUsage.value
|
||||
patchUsageForm.times_per_day =
|
||||
Number(rx.times_per_day) > 0 ? Number(rx.times_per_day) : 3
|
||||
patchUsageForm.usage_days =
|
||||
Number(rx.usage_days) > 0 ? Number(rx.usage_days) : 7
|
||||
patchUsageForm.aux_times_per_day =
|
||||
aux && Number(aux.times_per_day) > 0 ? Number(aux.times_per_day) : 3
|
||||
patchUsageForm.aux_usage_days =
|
||||
aux && Number(aux.usage_days) > 0 ? Number(aux.usage_days) : 7
|
||||
const md = Number(ord.medication_days)
|
||||
patchUsageForm.medication_days = md > 0 ? md : Number(rx.usage_days) > 0 ? Number(rx.usage_days) : 7
|
||||
patchUsageVisible.value = true
|
||||
nextTick(() => patchUsageFormRef.value?.clearValidate())
|
||||
}
|
||||
|
||||
function resetPatchUsageForm() {
|
||||
patchUsageForm.times_per_day = 3
|
||||
patchUsageForm.usage_days = 7
|
||||
patchUsageForm.aux_times_per_day = 3
|
||||
patchUsageForm.aux_usage_days = 7
|
||||
patchUsageForm.medication_days = undefined
|
||||
}
|
||||
|
||||
async function submitPatchUsage() {
|
||||
const form = patchUsageFormRef.value
|
||||
if (!form) return
|
||||
try {
|
||||
await form.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
const ordId = detailData.value?.id
|
||||
if (!ordId) return
|
||||
patchUsageSaving.value = true
|
||||
try {
|
||||
const payload: {
|
||||
id: number
|
||||
times_per_day: number
|
||||
usage_days: number
|
||||
medication_days: number
|
||||
aux_times_per_day?: number
|
||||
aux_usage_days?: number
|
||||
} = {
|
||||
id: ordId,
|
||||
times_per_day: Number(patchUsageForm.times_per_day),
|
||||
usage_days: Number(patchUsageForm.usage_days),
|
||||
medication_days: Number(patchUsageForm.medication_days)
|
||||
}
|
||||
if (detailHasAuxHerbs.value) {
|
||||
payload.aux_times_per_day = Number(patchUsageForm.aux_times_per_day)
|
||||
payload.aux_usage_days = Number(patchUsageForm.aux_usage_days)
|
||||
}
|
||||
await prescriptionOrderPatchPrescriptionUsage(payload)
|
||||
feedback.msgSuccess('保存成功')
|
||||
patchUsageVisible.value = false
|
||||
await refreshCurrentPrescriptionOrderDetail()
|
||||
await fetchLogs(ordId)
|
||||
getLists()
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
patchUsageSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openPatchRxPatientDialog() {
|
||||
const rx = detailPrescription.value
|
||||
const ord = detailData.value
|
||||
|
||||
@@ -39,8 +39,16 @@
|
||||
|
||||
<div class="grid grid-cols-4 gap-4 mb-4">
|
||||
<el-card shadow="hover">
|
||||
<div class="text-gray-500 text-sm mb-1">总客户数</div>
|
||||
<div class="text-gray-500 text-sm mb-1">总客户数(去重)</div>
|
||||
<div class="text-2xl font-bold text-primary">{{ stats.total }}</div>
|
||||
<el-tooltip
|
||||
content="企微管理后台「全部客户」按客户×添加人关系计数:同一客户被 N 名员工添加计 N 条。与企微对账请看此数字。"
|
||||
placement="bottom"
|
||||
>
|
||||
<div class="text-xs text-gray-400 mt-1 cursor-help">
|
||||
跟进关系数(企微口径):{{ stats.relation_total }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</el-card>
|
||||
<el-card shadow="hover" class="today-arrival-card cursor-pointer" @click="openArrivalDrawer">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
@@ -52,6 +60,9 @@
|
||||
<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 v-if="arrival.returning > 0" class="text-xs text-warning pb-1">
|
||||
含老客户 {{ arrival.returning }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 24 小时分布迷你柱:高度相对今日峰值等比 -->
|
||||
<div class="hourly-bars mt-2" :title="hourlyTooltip">
|
||||
@@ -114,6 +125,24 @@
|
||||
@change="onAddTimeRangeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<el-tooltip
|
||||
content="首次添加:客户第一次加入企业的时间在所选范围内。任意添加(企微口径):范围内发生过添加动作即算,含老客户被重加/被其他员工添加,与企微后台时间筛选一致。"
|
||||
placement="top"
|
||||
>
|
||||
<span class="cursor-help">时间口径</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-select
|
||||
v-model="queryParams.add_time_mode"
|
||||
class="!w-[180px]"
|
||||
@change="onAddTimeModeChange"
|
||||
>
|
||||
<el-option label="首次添加" value="first" />
|
||||
<el-option label="任意添加(企微口径)" value="any" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标签">
|
||||
<el-select
|
||||
v-model="queryParams.tag_ids"
|
||||
@@ -158,7 +187,23 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<el-avatar :size="40" :src="row.avatar" />
|
||||
<div>
|
||||
<div class="font-medium">{{ row.name }}</div>
|
||||
<div class="font-medium flex items-center gap-1">
|
||||
<span>{{ row.name }}</span>
|
||||
<el-tooltip
|
||||
v-if="row.readd_flag"
|
||||
content="这个人以前加过企业:被删除后重新添加,或已是企业客户又被其他员工添加"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag size="small" type="warning" effect="plain">以前加过</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
v-if="row.is_deleted"
|
||||
content="该客户已被删除/流失(企微中已不是企业客户),仅在「任意添加」口径下展示"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag size="small" type="danger" effect="plain">已删除</el-tag>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400">{{ row.external_userid }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -278,6 +323,9 @@
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-base font-semibold">今日进入明细</span>
|
||||
<el-tag type="success" effect="plain">共 {{ arrivalList.total }} 人</el-tag>
|
||||
<el-tag v-if="arrival.returning > 0" type="warning" effect="plain">
|
||||
老客户 {{ arrival.returning }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-button link type="primary" :loading="arrivalListLoading" @click="loadArrival(true)">
|
||||
<template #icon><Refresh /></template>
|
||||
@@ -331,8 +379,17 @@
|
||||
{{ (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 class="text-sm font-medium truncate flex items-center gap-1">
|
||||
<span class="truncate">
|
||||
{{ row.customer_name || row.external_userid || '(未知客户)' }}
|
||||
</span>
|
||||
<el-tooltip
|
||||
v-if="row.is_old_customer"
|
||||
content="老客户回流:今天之前就加过企业(删除后重加,或已是其他员工的客户)"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag size="small" type="warning" effect="plain">老客户</el-tag>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-0.5">
|
||||
<span>接待:{{ row.admin_name || row.user_id || '—' }}</span>
|
||||
@@ -469,6 +526,9 @@
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="类型">
|
||||
{{ currentCustomer.type === 1 ? '微信用户' : '企业微信用户' }}
|
||||
<el-tag v-if="currentCustomer.readd_flag" size="small" type="warning" effect="plain" class="ml-1">
|
||||
以前加过
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="企业名称" :span="2">
|
||||
{{ currentCustomer.corp_name || '—' }}
|
||||
@@ -544,6 +604,7 @@ const currentCustomer = ref<any>(null)
|
||||
|
||||
const stats = reactive({
|
||||
total: 0,
|
||||
relation_total: 0,
|
||||
today: 0,
|
||||
today_follow_staff: 0,
|
||||
lastSync: '',
|
||||
@@ -575,12 +636,14 @@ const queryParams = reactive<{
|
||||
tag_ids: string[]
|
||||
add_time_start: string
|
||||
add_time_end: string
|
||||
add_time_mode: 'first' | 'any'
|
||||
}>({
|
||||
name: '',
|
||||
follow_user: '',
|
||||
tag_ids: [],
|
||||
add_time_start: '',
|
||||
add_time_end: ''
|
||||
add_time_end: '',
|
||||
add_time_mode: 'first'
|
||||
})
|
||||
|
||||
/** 与列表「添加时间」列口径一致(库内 external_first_add_time / create_time) */
|
||||
@@ -597,6 +660,13 @@ function onAddTimeRangeChange(val: [string, string] | null) {
|
||||
resetPage()
|
||||
}
|
||||
|
||||
/** 口径切换只在已选时间范围时才需要重查 */
|
||||
function onAddTimeModeChange() {
|
||||
if (queryParams.add_time_start || queryParams.add_time_end) {
|
||||
resetPage()
|
||||
}
|
||||
}
|
||||
|
||||
// ── 标签维度(筛选下拉 + 抽屉面板共用同一份数据) ──────────────────────────
|
||||
interface TagItem {
|
||||
tag_id: string
|
||||
@@ -689,6 +759,7 @@ function filterByTag(tagId: string) {
|
||||
interface ArrivalStats {
|
||||
total: number
|
||||
recent_time: number
|
||||
returning: number
|
||||
hourly: number[]
|
||||
by_state: { state: string; count: number }[]
|
||||
}
|
||||
@@ -702,11 +773,13 @@ interface ArrivalItem {
|
||||
customer_avatar: string
|
||||
state: string
|
||||
welcome_code: number
|
||||
is_old_customer: number
|
||||
}
|
||||
|
||||
const arrival = reactive<ArrivalStats>({
|
||||
total: 0,
|
||||
recent_time: 0,
|
||||
returning: 0,
|
||||
hourly: new Array(24).fill(0),
|
||||
by_state: []
|
||||
})
|
||||
@@ -740,6 +813,7 @@ async function loadArrival(refreshList = false) {
|
||||
if (res) {
|
||||
arrival.total = Number(res.total ?? 0)
|
||||
arrival.recent_time = Number(res.recent_time ?? 0)
|
||||
arrival.returning = Number(res.returning ?? 0)
|
||||
arrival.hourly = Array.isArray(res.hourly) && res.hourly.length === 24
|
||||
? res.hourly.map((n: any) => Number(n) || 0)
|
||||
: new Array(24).fill(0)
|
||||
@@ -829,6 +903,7 @@ function handleReset() {
|
||||
queryParams.tag_ids = []
|
||||
queryParams.add_time_start = ''
|
||||
queryParams.add_time_end = ''
|
||||
queryParams.add_time_mode = 'first'
|
||||
addTimeRange.value = null
|
||||
resetParams()
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<el-tree-select
|
||||
v-model="deptId"
|
||||
:data="deptTreeOptions"
|
||||
placeholder="全部部门"
|
||||
placeholder="二中心(全部)"
|
||||
clearable
|
||||
filterable
|
||||
check-strictly
|
||||
@@ -43,10 +43,10 @@
|
||||
</template>
|
||||
<div class="rate-caliber">
|
||||
<p>
|
||||
<b>当月被指派总数</b>:当月内诊单被指派给医助(按指派操作时间落月,<b>剔除勾选「继承」的指派</b>)的诊单数,按「医助 × 诊单」去重;部门行 / 合计行按诊单去重。
|
||||
<b>当月被指派总数</b>:当月内诊单被指派给医助(按指派操作时间落月,<b>剔除勾选「继承」的指派</b>)的诊单数,按「医助 × 诊单」去重;部门行 / 合计行按诊单去重;<b>再剔除</b>名下存在履约「拒收 / 退款」业务订单的诊单。
|
||||
</p>
|
||||
<p>
|
||||
<b>诊次(第 N 次下单)</b>:患者(诊单)名下计入业绩的业务订单(剔除已取消 / 拒收 / 退款)按下单时间升序的全局序号,<b>跨月累计不重置</b>——如 5 月指派后旗下成交 4 单为二诊~五诊,下月再成交即为六诊。
|
||||
<b>诊次(第 N 次下单)</b>:患者(诊单)名下计入业绩的业务订单(剔除已取消 / 拒收 / 退款)按下单时间升序编号为「实单序号」,<b>统计诊次 = 实单序号 + 诊单偏移</b>(默认偏移 0 → 第 1 笔实单为一诊;偏移 1 → 第 1 笔实单为二诊;偏移 2 → 第 1 笔实单为三诊,5 笔实单等价七诊)。诊次<b>跨月累计不重置</b>。诊单可在「业务订单」tab 配置偏移量。
|
||||
</p>
|
||||
<p>
|
||||
<b>当月 N 诊单数</b>:当月内下单且诊次为 N 的订单数,归属下单时点<b>持有该患者的医助</b>(指派可在往月;释放后不再归属;「继承」指派会转移持有人但不计被指派数)。
|
||||
@@ -55,7 +55,7 @@
|
||||
<b>当月 N 诊接诊率</b> = 当月 N 诊单数 ÷ 当月被指派总数。往月指派、当月成交会推高分子,比率可能超过 100%;医助当月无新指派但旗下有成交时,被指派数为 0、比率显示「—」。
|
||||
</p>
|
||||
<p>
|
||||
医助按人事部门归组;选定部门时含其组织下级。
|
||||
医助按人事部门归组;<b>仅统计「二中心」及其组织下级</b>;部门下拉与未选时的默认范围均限定在该子树,选定部门时含其组织下级。
|
||||
</p>
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
@@ -83,6 +83,23 @@
|
||||
<el-radio-button value="0">未确认</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">部门</span>
|
||||
<el-tree-select
|
||||
v-model="formData.assistant_dept_id"
|
||||
:data="departmentTreeRaw"
|
||||
class="filter-dept-select"
|
||||
clearable
|
||||
filterable
|
||||
check-strictly
|
||||
:default-expand-all="true"
|
||||
node-key="id"
|
||||
size="small"
|
||||
:props="assistantDeptTreeProps"
|
||||
placeholder="选父级含子级"
|
||||
@change="handleAssistantDeptChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
@@ -542,6 +559,7 @@
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import { defineAsyncComponent, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { appointmentLists, cancelAppointment, completeAppointment, appointmentDetail } from '@/api/doctor'
|
||||
import { deptAll } from '@/api/org/department'
|
||||
import { getCallSignature, generateMiniProgramQrcode, tcmDiagnosisDetail, prescriptionGetByAppointment } from '@/api/tcm'
|
||||
import { getDictData } from '@/api/app'
|
||||
import { addDoctorNote } from '@/api/patient'
|
||||
@@ -603,10 +621,19 @@ const formData = reactive({
|
||||
end_date: '',
|
||||
date_preset: 'today' as '' | 'yesterday' | 'day_before' | 'today' | 'tomorrow' | 'day_after',
|
||||
diagnosis_confirmed: '' as '' | '0' | '1', // ''=全部 1=已确认 0=未确认
|
||||
/** 接诊医生 / 诊单医助 / 挂号医助所属部门(选父级含子级) */
|
||||
assistant_dept_id: '' as number | '',
|
||||
/** 为 1 时后端 extend 返回各状态数量,避免额外 4 次列表请求 */
|
||||
include_status_counts: 0 as 0 | 1
|
||||
})
|
||||
|
||||
const departmentTreeRaw = ref<unknown[]>([])
|
||||
const assistantDeptTreeProps = {
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
}
|
||||
|
||||
const activeTab = ref('1')
|
||||
const dateCustomVisible = ref(false)
|
||||
const statusCount = ref<Record<number, number>>({
|
||||
@@ -721,6 +748,12 @@ const handleDiagnosisConfirmedChange = () => {
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 部门筛选变更
|
||||
const handleAssistantDeptChange = () => {
|
||||
pager.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
// 快捷日期变更
|
||||
const handleDatePresetChange = (val: string | number | boolean | undefined) => {
|
||||
const v = String(val || '')
|
||||
@@ -770,6 +803,7 @@ const handleReset = () => {
|
||||
formData.doctor_name = ''
|
||||
formData.date_preset = 'today'
|
||||
formData.diagnosis_confirmed = ''
|
||||
formData.assistant_dept_id = ''
|
||||
const t = new Date()
|
||||
const p = (n: number) => String(n).padStart(2, '0')
|
||||
formData.start_date = `${t.getFullYear()}-${p(t.getMonth() + 1)}-${p(t.getDate())}`
|
||||
@@ -1099,7 +1133,13 @@ formData.start_date = `${_today.getFullYear()}-${_pad(_today.getMonth() + 1)}-${
|
||||
formData.end_date = formData.start_date
|
||||
formData.status = 1
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const deptTree = await deptAll()
|
||||
departmentTreeRaw.value = Array.isArray(deptTree) ? deptTree : []
|
||||
} catch {
|
||||
departmentTreeRaw.value = []
|
||||
}
|
||||
loadData()
|
||||
listPollTimer = setInterval(() => {
|
||||
loadData({ silent: true })
|
||||
@@ -1228,6 +1268,10 @@ onUnmounted(() => {
|
||||
padding: 6px 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-dept-select {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,43 @@
|
||||
<el-empty description="当前诊单未携带患者ID,无法列出业务订单" />
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-if="diagnosisId > 0" class="po-revisit-offset-bar mb-3">
|
||||
<div class="po-revisit-offset-bar__main">
|
||||
<span class="text-sm text-gray-600">复诊统计起始偏移</span>
|
||||
<el-tooltip placement="top">
|
||||
<template #content>
|
||||
<div class="max-w-xs leading-relaxed">
|
||||
在实单诊次序号上叠加偏移量。设为 0(默认):第 1 笔实单计为一诊;设为 1:第 1 笔实单计为二诊;设为 2:第 1 笔实单计为三诊——若有 5 笔实单且偏移 2,则统计上相当于计至七诊(5+2)。
|
||||
</div>
|
||||
</template>
|
||||
<el-icon class="text-gray-400 align-middle ml-1"><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
<el-input-number
|
||||
v-model="revisitSlotStartOffset"
|
||||
v-perms="['tcm.diagnosis/setRevisitSlotStartOffset']"
|
||||
:min="0"
|
||||
:max="20"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
class="w-[120px] ml-3"
|
||||
:disabled="offsetSaving"
|
||||
/>
|
||||
<span class="text-xs text-gray-500 ml-2">
|
||||
第 1 笔实单计为{{ visitSlotStartLabel }}
|
||||
</span>
|
||||
</div>
|
||||
<el-button
|
||||
v-perms="['tcm.diagnosis/setRevisitSlotStartOffset']"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="offsetSaving"
|
||||
:disabled="!offsetDirty"
|
||||
@click="saveRevisitSlotStartOffset"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="pager.loading"
|
||||
:data="pager.lists"
|
||||
@@ -12,6 +49,23 @@
|
||||
empty-text="暂无业务订单"
|
||||
>
|
||||
<el-table-column label="订单编号" prop="order_no" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="全局诊次" width="96" align="center">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.global_visit_seq">{{ row.global_visit_seq }}诊</span>
|
||||
<span v-else class="text-gray-400">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计入统计" width="96" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-if="row.counts_for_revisit_rate"
|
||||
type="success"
|
||||
size="small"
|
||||
effect="plain"
|
||||
>是</el-tag>
|
||||
<el-tag v-else type="info" size="small" effect="plain">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" width="120" align="right">
|
||||
<template #default="{ row }">
|
||||
<span class="text-red-500 font-semibold">¥{{ formatAmount(row.amount) }}</span>
|
||||
@@ -63,9 +117,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import { prescriptionOrderLists } from '@/api/tcm'
|
||||
import { prescriptionOrderLists, tcmDiagnosisDetail, tcmDiagnosisSetRevisitSlotStartOffset } from '@/api/tcm'
|
||||
import PrescriptionOrderDetailDrawer from '@/views/consumer/prescription/components/PrescriptionOrderDetailDrawer.vue'
|
||||
import feedback from '@/utils/feedback'
|
||||
import {
|
||||
formatTime,
|
||||
fulfillmentText,
|
||||
@@ -91,6 +147,26 @@ const { pager, getLists, resetPage } = usePaging({
|
||||
size: 10
|
||||
})
|
||||
|
||||
const revisitSlotStartOffset = ref(0)
|
||||
const savedRevisitSlotStartOffset = ref(0)
|
||||
const offsetSaving = ref(false)
|
||||
const offsetLoading = ref(false)
|
||||
|
||||
const offsetDirty = computed(
|
||||
() => Number(revisitSlotStartOffset.value) !== Number(savedRevisitSlotStartOffset.value)
|
||||
)
|
||||
|
||||
const visitSlotStartLabel = computed(() => {
|
||||
const raw = Number(revisitSlotStartOffset.value)
|
||||
const offset = Number.isFinite(raw) ? raw : 0
|
||||
const slot = offset + 1
|
||||
const cn = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
||||
if (slot >= 1 && slot <= 10) {
|
||||
return cn[slot] + '诊'
|
||||
}
|
||||
return `第${slot}诊`
|
||||
})
|
||||
|
||||
const buildParams = () => {
|
||||
Object.keys(queryParams).forEach((k) => delete queryParams[k])
|
||||
if (props.diagnosisId > 0) {
|
||||
@@ -102,6 +178,47 @@ const buildParams = () => {
|
||||
queryParams.scene = 'diagnosis_edit'
|
||||
}
|
||||
|
||||
async function loadRevisitSlotStartOffset() {
|
||||
if (props.diagnosisId <= 0) return
|
||||
offsetLoading.value = true
|
||||
try {
|
||||
const res: any = await tcmDiagnosisDetail({ id: props.diagnosisId })
|
||||
const d = res?.data ?? res ?? {}
|
||||
const offset = Number(d.revisit_slot_start_offset)
|
||||
const val = Number.isFinite(offset) && offset >= 0 && offset <= 20 ? offset : 0
|
||||
revisitSlotStartOffset.value = val
|
||||
savedRevisitSlotStartOffset.value = val
|
||||
} catch {
|
||||
revisitSlotStartOffset.value = 0
|
||||
savedRevisitSlotStartOffset.value = 0
|
||||
} finally {
|
||||
offsetLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveRevisitSlotStartOffset() {
|
||||
if (props.diagnosisId <= 0) return
|
||||
const offset = Number(revisitSlotStartOffset.value)
|
||||
if (!Number.isFinite(offset) || offset < 0 || offset > 20) {
|
||||
feedback.msgWarning('起始偏移须在 0~20 之间')
|
||||
return
|
||||
}
|
||||
offsetSaving.value = true
|
||||
try {
|
||||
await tcmDiagnosisSetRevisitSlotStartOffset({
|
||||
id: props.diagnosisId,
|
||||
revisit_slot_start_offset: offset
|
||||
})
|
||||
savedRevisitSlotStartOffset.value = offset
|
||||
feedback.msgSuccess('保存成功')
|
||||
getLists()
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
offsetSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 详情抽屉(共享组件,数据拉取/展示全部在组件内) ───
|
||||
const detailDrawerRef = ref<InstanceType<typeof PrescriptionOrderDetailDrawer>>()
|
||||
|
||||
@@ -117,8 +234,13 @@ const formatAmount = (value: unknown) => {
|
||||
watch(
|
||||
() => [props.diagnosisId, patientIdNum.value] as const,
|
||||
() => {
|
||||
if (!patientIdAvailable.value) { pager.lists = []; pager.count = 0; return }
|
||||
if (!patientIdAvailable.value) {
|
||||
pager.lists = []
|
||||
pager.count = 0
|
||||
return
|
||||
}
|
||||
buildParams()
|
||||
void loadRevisitSlotStartOffset()
|
||||
resetPage()
|
||||
},
|
||||
{ immediate: true }
|
||||
@@ -134,4 +256,21 @@ defineExpose({ refresh: () => getLists() })
|
||||
.po-empty-tip {
|
||||
padding: 24px 0;
|
||||
}
|
||||
.po-revisit-offset-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
background: var(--el-fill-color-lighter);
|
||||
}
|
||||
.po-revisit-offset-bar__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -94,6 +94,24 @@ class DiagnosisController extends BaseAdminController
|
||||
return $this->fail(DiagnosisLogic::getError());
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 设置复诊接诊率统计起始偏移(业务订单 tab)
|
||||
*/
|
||||
public function setRevisitSlotStartOffset()
|
||||
{
|
||||
$params = (new DiagnosisValidate())->post()->goCheck('setRevisitSlotStartOffset');
|
||||
$ok = DiagnosisLogic::setRevisitSlotStartOffset(
|
||||
(int) $params['id'],
|
||||
(int) $params['revisit_slot_start_offset'],
|
||||
$this->adminInfo
|
||||
);
|
||||
if (!$ok) {
|
||||
return $this->fail(DiagnosisLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('保存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 删除诊单
|
||||
* @return \think\response\Json
|
||||
|
||||
@@ -181,6 +181,20 @@ class PrescriptionOrderController extends BaseAdminController
|
||||
return $this->success('保存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改关联处方服用参数(主方/辅方次数与开立天数)及订单服用天数
|
||||
*/
|
||||
public function patchPrescriptionUsage()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->post()->goCheck('patchPrescriptionUsage');
|
||||
$ok = PrescriptionOrderLogic::patchPrescriptionUsage($params, $this->adminId, $this->adminInfo);
|
||||
if (!$ok) {
|
||||
return $this->fail(PrescriptionOrderLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('保存成功');
|
||||
}
|
||||
|
||||
public function auditPrescription()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->post()->goCheck('auditPrescription');
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace app\adminapi\lists\doctor;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\dept\DeptLogic;
|
||||
use app\common\model\auth\AdminDept;
|
||||
use app\common\model\DiagnosisViewRecord;
|
||||
use app\common\model\doctor\Appointment;
|
||||
use app\common\model\tcm\Prescription;
|
||||
@@ -75,6 +77,35 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 按部门筛选:接诊医生、诊单医助或挂号医助所属部门命中子树即可(选父级含子级)
|
||||
*
|
||||
* @param mixed $query
|
||||
*/
|
||||
private function applyAssistantDeptIdFilter($query): void
|
||||
{
|
||||
if (!isset($this->params['assistant_dept_id']) || $this->params['assistant_dept_id'] === '' || (int) $this->params['assistant_dept_id'] <= 0) {
|
||||
return;
|
||||
}
|
||||
$rootDeptId = (int) $this->params['assistant_dept_id'];
|
||||
$deptIds = DeptLogic::getSelfAndDescendantIds($rootDeptId);
|
||||
$deptIds = array_values(array_filter(array_map('intval', $deptIds), static function (int $id): bool {
|
||||
return $id > 0;
|
||||
}));
|
||||
if ($deptIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
$inList = implode(',', $deptIds);
|
||||
$adTbl = (new AdminDept())->getTable();
|
||||
$query->whereRaw(
|
||||
"(EXISTS (SELECT 1 FROM `{$adTbl}` ad WHERE ad.`admin_id` = a.`doctor_id` AND ad.`dept_id` IN ({$inList}))"
|
||||
. " OR EXISTS (SELECT 1 FROM `{$adTbl}` ad WHERE ad.`admin_id` = u.`assistant_id` AND ad.`dept_id` IN ({$inList}))"
|
||||
. " OR EXISTS (SELECT 1 FROM `{$adTbl}` ad WHERE ad.`admin_id` = a.`assistant_id` AND ad.`dept_id` IN ({$inList})))"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渠道筛选:与 AppointmentLogic 一致,兼容仅有 channel_source、仅有 channels、或两者皆有的表结构
|
||||
*
|
||||
@@ -191,6 +222,8 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
|
||||
$this->applyAssistantIdFilter($query);
|
||||
|
||||
$this->applyAssistantDeptIdFilter($query);
|
||||
|
||||
$this->applyChannelSourceFilter($query, $chFilter);
|
||||
|
||||
// 是否确认诊单:1=已确认 0=未确认
|
||||
@@ -373,6 +406,8 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
|
||||
$this->applyAssistantIdFilter($query);
|
||||
|
||||
$this->applyAssistantDeptIdFilter($query);
|
||||
|
||||
$this->applyChannelSourceFilter($query, $chFilter);
|
||||
|
||||
if ((int) ($this->params['exclude_cancelled'] ?? 0) === 1) {
|
||||
|
||||
@@ -77,20 +77,37 @@ class CustomerLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
}
|
||||
}
|
||||
|
||||
// 添加时间:与 lists 排序口径一致(external_first_add_time 优先,0 则 create_time)
|
||||
// 添加时间筛选,两种口径:
|
||||
// first(默认)= 首次添加时间在窗口内(external_first_add_time 优先,0 则 create_time)
|
||||
// any = 企微「全部客户+时间筛选」口径:存在"添加时间在窗口内的现存跟进关系"即命中
|
||||
// (含老客户被重加/被其他员工添加;关系已解除的不算),数据源为现存关系表
|
||||
$addStart = trim((string) ($this->params['add_time_start'] ?? ''));
|
||||
$addEnd = trim((string) ($this->params['add_time_end'] ?? ''));
|
||||
$effExpr = 'COALESCE(NULLIF(external_first_add_time, 0), create_time)';
|
||||
if ($addStart !== '') {
|
||||
$t = strtotime($addStart . ' 00:00:00');
|
||||
if ($t !== false) {
|
||||
$query->whereRaw($effExpr . ' > 0 AND ' . $effExpr . ' >= ?', [$t]);
|
||||
$addMode = trim((string) ($this->params['add_time_mode'] ?? 'first'));
|
||||
$startTs = $addStart !== '' ? strtotime($addStart . ' 00:00:00') : false;
|
||||
$endTs = $addEnd !== '' ? strtotime($addEnd . ' 23:59:59') : false;
|
||||
|
||||
if ($addMode === 'any' && ($startTs !== false || $endTs !== false)) {
|
||||
$followQuery = Db::name('qywx_external_contact_follow')->where('createtime', '>', 0);
|
||||
if ($startTs !== false) {
|
||||
$followQuery->where('createtime', '>=', $startTs);
|
||||
}
|
||||
}
|
||||
if ($addEnd !== '') {
|
||||
$t = strtotime($addEnd . ' 23:59:59');
|
||||
if ($t !== false) {
|
||||
$query->whereRaw($effExpr . ' > 0 AND ' . $effExpr . ' <= ?', [$t]);
|
||||
if ($endTs !== false) {
|
||||
$followQuery->where('createtime', '<=', $endTs);
|
||||
}
|
||||
$matchedExtIds = $followQuery->group('external_userid')->column('external_userid');
|
||||
if ($matchedExtIds === []) {
|
||||
$query->whereRaw('1=0');
|
||||
} else {
|
||||
$query->whereIn('external_userid', $matchedExtIds);
|
||||
}
|
||||
} else {
|
||||
$effExpr = 'COALESCE(NULLIF(external_first_add_time, 0), create_time)';
|
||||
if ($startTs !== false) {
|
||||
$query->whereRaw($effExpr . ' > 0 AND ' . $effExpr . ' >= ?', [$startTs]);
|
||||
}
|
||||
if ($endTs !== false) {
|
||||
$query->whereRaw($effExpr . ' > 0 AND ' . $effExpr . ' <= ?', [$endTs]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +175,9 @@ class CustomerLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
$fromDb = (int) ($item['external_first_add_time'] ?? 0);
|
||||
$fromJson = CustomerLogic::minFollowCreatetime($followUsers);
|
||||
$item['external_first_add_time'] = $fromDb > 0 ? $fromDb : $fromJson;
|
||||
|
||||
// 「任意添加」口径会带出软删行,前端据此显示"已删除"标记
|
||||
$item['is_deleted'] = !empty($item['delete_time']) ? 1 : 0;
|
||||
}
|
||||
unset($item);
|
||||
|
||||
|
||||
@@ -741,6 +741,10 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
}
|
||||
unset($item);
|
||||
|
||||
if ($this->shouldBypassListVisibilityForDiagnosisEdit()) {
|
||||
$this->appendDiagnosisEditVisitSeqFields($lists);
|
||||
}
|
||||
|
||||
$this->appendPrescriptionOrderAssignSnapshotErCenterFlags($lists);
|
||||
|
||||
if ((int) ($this->params['yeji_order_drawer'] ?? 0) === 1) {
|
||||
@@ -1417,6 +1421,69 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊单编辑-业务订单 tab:标注全局诊次及是否计入复诊接诊率(与 RevisitRateLogic 同口径)
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $lists
|
||||
*/
|
||||
private function appendDiagnosisEditVisitSeqFields(array &$lists): void
|
||||
{
|
||||
if ($lists === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$diagIds = [];
|
||||
foreach ($lists as $row) {
|
||||
$d = (int) ($row['diagnosis_id'] ?? 0);
|
||||
if ($d > 0) {
|
||||
$diagIds[$d] = true;
|
||||
}
|
||||
}
|
||||
$contextDid = (int) ($this->params['context_diagnosis_id'] ?? 0);
|
||||
if ($contextDid > 0) {
|
||||
$diagIds[$contextDid] = true;
|
||||
}
|
||||
$diagIdList = array_keys($diagIds);
|
||||
if ($diagIdList === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$offsetRows = Diagnosis::whereIn('id', $diagIdList)
|
||||
->whereNull('delete_time')
|
||||
->column('revisit_slot_start_offset', 'id');
|
||||
$offsetMap = [];
|
||||
foreach ($offsetRows as $id => $offset) {
|
||||
$offsetMap[(int) $id] = max(0, min(20, (int) $offset));
|
||||
}
|
||||
|
||||
/** @var array<int, int> $seqByOrderId order_id => global seq within diagnosis */
|
||||
$seqByOrderId = [];
|
||||
foreach ($diagIdList as $did) {
|
||||
$q = PrescriptionOrder::where('diagnosis_id', $did)->whereNull('delete_time');
|
||||
YejiStatsLogic::applyPrescriptionOrderNotCancelledForPerformanceQuery($q, '');
|
||||
$orderIds = $q
|
||||
->order(['create_time' => 'asc', 'id' => 'asc'])
|
||||
->column('id');
|
||||
$seq = 0;
|
||||
foreach ($orderIds as $oid) {
|
||||
$seq++;
|
||||
$seqByOrderId[(int) $oid] = $seq;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($lists as &$row) {
|
||||
$oid = (int) ($row['id'] ?? 0);
|
||||
$did = (int) ($row['diagnosis_id'] ?? 0);
|
||||
$seq = (int) ($seqByOrderId[$oid] ?? 0);
|
||||
$offset = (int) ($offsetMap[$did] ?? 0);
|
||||
$effectiveSlot = $seq > 0 ? $seq + $offset : 0;
|
||||
$row['global_visit_seq'] = $effectiveSlot > 0 ? $effectiveSlot : null;
|
||||
$row['raw_visit_seq'] = $seq > 0 ? $seq : null;
|
||||
$row['counts_for_revisit_rate'] = $effectiveSlot >= 2 ? 1 : 0;
|
||||
}
|
||||
unset($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表行标注:指派日志快照(related_po_creator_id + related_po_create_time)是否指向本业务单,
|
||||
* 以及该次操作的新医助(to_assistant_id)是否归属「二中心」部门子树(与 DeptLogic / 业绩看板一致)。
|
||||
|
||||
@@ -151,6 +151,24 @@ class DeptLogic extends BaseLogic
|
||||
* @return list<int>
|
||||
*/
|
||||
private static function findErCenterRootDeptIds(): array
|
||||
{
|
||||
return self::findCenterRootDeptIdsByNameKeyword('二中心');
|
||||
}
|
||||
|
||||
/**
|
||||
* 名称含「一中心」的部门 id。
|
||||
*
|
||||
* @return list<int>
|
||||
*/
|
||||
private static function findYiCenterRootDeptIds(): array
|
||||
{
|
||||
return self::findCenterRootDeptIdsByNameKeyword('一中心');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<int>
|
||||
*/
|
||||
private static function findCenterRootDeptIdsByNameKeyword(string $keyword): array
|
||||
{
|
||||
$rows = Dept::whereNull('delete_time')
|
||||
->field(['id', 'name'])
|
||||
@@ -159,7 +177,7 @@ class DeptLogic extends BaseLogic
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$name = (string) ($r['name'] ?? '');
|
||||
if ($name !== '' && mb_strpos($name, '二中心') !== false) {
|
||||
if ($name !== '' && mb_strpos($name, $keyword) !== false) {
|
||||
$out[] = (int) $r['id'];
|
||||
}
|
||||
}
|
||||
@@ -201,6 +219,19 @@ class DeptLogic extends BaseLogic
|
||||
return array_fill_keys($subtreeIds, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 名称含「一中心」的部门及其全部下级 id(map)。
|
||||
*
|
||||
* @return array<int, true>
|
||||
*/
|
||||
public static function getYiCenterSubtreeDeptIdSet(): array
|
||||
{
|
||||
$yiRoots = self::findYiCenterRootDeptIds();
|
||||
$subtreeIds = self::unionErCenterSubtreeDeptIds($yiRoots);
|
||||
|
||||
return array_fill_keys($subtreeIds, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 二中心复诊统计用的业务订单行(与 rollup 同源 SQL)。
|
||||
*
|
||||
|
||||
@@ -585,15 +585,32 @@ class CustomerLogic extends BaseLogic
|
||||
/**
|
||||
* 客户联系事件回调:按 external_userid 拉取详情并 UPSERT(不走全量同步文件锁)。
|
||||
*
|
||||
* @param bool $isAddEvent 是否 add_external_contact 事件:是且本地已有该客户(含软删行)时,
|
||||
* 新增一行 readd_flag=1(以前加过)的记录,原有行不动
|
||||
* @param string $eventUserId 本次添加人的企微 userid(add 事件回调里的 UserID,用于取本次添加时间)
|
||||
* @param int $eventTime 回调事件时间(企微 CreateTime,秒)
|
||||
* @see https://developer.work.weixin.qq.com/document/path/92130
|
||||
*/
|
||||
public static function upsertSingleExternalContactFromApi(string $externalUserId): void
|
||||
{
|
||||
public static function upsertSingleExternalContactFromApi(
|
||||
string $externalUserId,
|
||||
bool $isAddEvent = false,
|
||||
string $eventUserId = '',
|
||||
int $eventTime = 0
|
||||
): void {
|
||||
$externalUserId = trim($externalUserId);
|
||||
$eventUserId = trim($eventUserId);
|
||||
if ($externalUserId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 必须在写库之前判断"是否已存在",否则无法区分新客户和重加客户
|
||||
$existedBefore = false;
|
||||
if ($isAddEvent) {
|
||||
$existedBefore = Db::name('qywx_external_contact')
|
||||
->where('external_userid', $externalUserId)
|
||||
->count() > 0;
|
||||
}
|
||||
|
||||
$service = new WechatWorkService();
|
||||
$detail = [];
|
||||
$lastError = null;
|
||||
@@ -619,6 +636,14 @@ class CustomerLogic extends BaseLogic
|
||||
if (empty($detail['external_contact']) || !is_array($detail['external_contact'])) {
|
||||
$errcode = is_array($lastError) ? ($lastError['errcode'] ?? null) : null;
|
||||
$errmsg = is_array($lastError) ? (string) ($lastError['errmsg'] ?? '') : '';
|
||||
// 84061 = 已不是企业客户(双向解除/员工删除后无人跟进):与企微对齐,本地软删。
|
||||
// 注意:客户单方删除员工(del_follow_user)时企微仍保留客户,get 会成功返回,不会走到这里。
|
||||
if ($errcode === 84061) {
|
||||
self::softDeleteExternalContactRow($externalUserId);
|
||||
Log::info('qywx external contact callback: 企微侧已不存在(84061),本地软删 ext=' . $externalUserId);
|
||||
|
||||
return;
|
||||
}
|
||||
Log::warning(sprintf(
|
||||
'qywx external contact callback: 拉取详情为空,跳过 UPSERT errcode=%s errmsg=%s ext=%s',
|
||||
$errcode === null ? '?' : (string) $errcode,
|
||||
@@ -634,6 +659,18 @@ class CustomerLogic extends BaseLogic
|
||||
$followUsers = [];
|
||||
}
|
||||
|
||||
// 重复添加:不动原有行,新增一行并打「以前加过」标记(与企微"每次添加都算一条"对齐)
|
||||
if ($isAddEvent && $existedBefore) {
|
||||
self::insertReaddContactRow(
|
||||
$detail['external_contact'],
|
||||
$followUsers,
|
||||
$eventUserId,
|
||||
$eventTime
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$syncCount = 0;
|
||||
$newCount = 0;
|
||||
$updateCount = 0;
|
||||
@@ -652,6 +689,48 @@ class CustomerLogic extends BaseLogic
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重复添加:为已存在的客户新增一行记录(原有行保持不变)。
|
||||
*
|
||||
* 新行的「添加时间」取本次添加动作的时间:优先本次添加人($eventUserId)在 follow_user
|
||||
* 里的 createtime,其次回调事件时间,最后兜底当前时间——保证这行出现在"本次添加"的日期下。
|
||||
*
|
||||
* @param array<string, mixed> $externalContact
|
||||
* @param array<int, array<string, mixed>> $followUsers
|
||||
*/
|
||||
private static function insertReaddContactRow(
|
||||
array $externalContact,
|
||||
array $followUsers,
|
||||
string $eventUserId,
|
||||
int $eventTime
|
||||
): void {
|
||||
$row = self::buildContactRow($externalContact, $followUsers);
|
||||
|
||||
$addTime = 0;
|
||||
if ($eventUserId !== '') {
|
||||
foreach ($followUsers as $fu) {
|
||||
if (is_array($fu) && trim((string) ($fu['userid'] ?? '')) === $eventUserId) {
|
||||
$addTime = self::normalizeFollowUserCreatetime($fu);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($addTime <= 0) {
|
||||
$addTime = $eventTime > 0 ? $eventTime : time();
|
||||
}
|
||||
|
||||
$row['external_first_add_time'] = $addTime;
|
||||
$row['readd_flag'] = 1;
|
||||
|
||||
Db::name('qywx_external_contact')->insert($row);
|
||||
|
||||
$extId = (string) $row['external_userid'];
|
||||
self::syncContactTagsRelation($extId, $followUsers);
|
||||
self::syncContactFollowRelation($extId, $followUsers);
|
||||
|
||||
Log::info('qywx external contact callback: 重复添加,新增一行(readd_flag=1) ext=' . $extId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件流水(零误差进入计数)
|
||||
*
|
||||
@@ -720,7 +799,7 @@ class CustomerLogic extends BaseLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户联系「删除企业客户」等事件:本地软删除一行。
|
||||
* 客户彻底流失(企微 get 返回 84061):该客户的所有行(含历史重加行)一并软删。
|
||||
*/
|
||||
public static function softDeleteExternalContactRow(string $externalUserId): void
|
||||
{
|
||||
@@ -739,84 +818,9 @@ class CustomerLogic extends BaseLogic
|
||||
Db::name('qywx_external_contact_tag')
|
||||
->where('external_userid', $externalUserId)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* `del_follow_user` 事件:某员工不再跟进该客户。
|
||||
* 只更新本地 `follow_users` JSON:移除匹配的 userid;若已无跟进人则软删该行。
|
||||
* 不再回调 /externalcontact/get,避免 84061「not external contact」刷 warning。
|
||||
*/
|
||||
public static function removeFollowUserFromLocal(string $externalUserId, string $userId): void
|
||||
{
|
||||
$externalUserId = trim($externalUserId);
|
||||
$userId = trim($userId);
|
||||
if ($externalUserId === '' || $userId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$row = Db::name('qywx_external_contact')
|
||||
Db::name('qywx_external_contact_follow')
|
||||
->where('external_userid', $externalUserId)
|
||||
->find();
|
||||
if (!$row) {
|
||||
return;
|
||||
}
|
||||
|
||||
$followUsers = json_decode((string) ($row['follow_users'] ?? '[]'), true);
|
||||
$followUsers = is_array($followUsers) ? $followUsers : [];
|
||||
$kept = [];
|
||||
$removed = false;
|
||||
foreach ($followUsers as $fu) {
|
||||
if (!is_array($fu)) {
|
||||
continue;
|
||||
}
|
||||
$uid = trim((string) ($fu['userid'] ?? ''));
|
||||
if ($uid === $userId) {
|
||||
$removed = true;
|
||||
continue;
|
||||
}
|
||||
$kept[] = $fu;
|
||||
}
|
||||
|
||||
if (!$removed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
if ($kept === []) {
|
||||
Db::name('qywx_external_contact')
|
||||
->where('id', (int) $row['id'])
|
||||
->update([
|
||||
'follow_users' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'tags' => '[]',
|
||||
'delete_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
|
||||
// 整客户已无人跟进 → 关系表清空
|
||||
Db::name('qywx_external_contact_tag')
|
||||
->where('external_userid', $externalUserId)
|
||||
->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$minCreate = self::minFollowCreatetime($kept);
|
||||
$update = [
|
||||
'follow_users' => json_encode($kept, JSON_UNESCAPED_UNICODE),
|
||||
'tags' => self::extractFollowUserTags($kept),
|
||||
'update_time' => $now,
|
||||
];
|
||||
if ($minCreate > 0) {
|
||||
// 移除最早那条跟进人后,首次添加时间可能后移;重新刷新字段以与 JSON 保持一致
|
||||
$update['external_first_add_time'] = $minCreate;
|
||||
}
|
||||
|
||||
Db::name('qywx_external_contact')
|
||||
->where('id', (int) $row['id'])
|
||||
->update($update);
|
||||
|
||||
// 关系表按剩余 kept follow_users 同步(自动清掉离开员工那行 + 保留其他员工的标签)
|
||||
self::syncContactTagsRelation($externalUserId, $kept);
|
||||
->delete();
|
||||
}
|
||||
|
||||
private static function upsertOneExternalContactBundle(
|
||||
@@ -845,32 +849,16 @@ class CustomerLogic extends BaseLogic
|
||||
}
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$row = [
|
||||
'external_userid' => $externalContact['external_userid'] ?? '',
|
||||
'name' => $externalContact['name'] ?? '',
|
||||
'avatar' => $externalContact['avatar'] ?? '',
|
||||
'type' => $externalContact['type'] ?? 1,
|
||||
'gender' => $externalContact['gender'] ?? 0,
|
||||
'unionid' => $externalContact['unionid'] ?? '',
|
||||
'position' => $externalContact['position'] ?? '',
|
||||
'corp_name' => $externalContact['corp_name'] ?? '',
|
||||
'corp_full_name' => $externalContact['corp_full_name'] ?? '',
|
||||
'external_profile' => json_encode($externalContact['external_profile'] ?? [], JSON_UNESCAPED_UNICODE),
|
||||
'follow_users' => json_encode($followUsers, JSON_UNESCAPED_UNICODE),
|
||||
'tags' => self::extractFollowUserTags($followUsers),
|
||||
'follow_admin_ids' => self::resolveFollowAdminIds($followUsers),
|
||||
'external_first_add_time' => self::minFollowCreatetime($followUsers),
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
'delete_time' => null,
|
||||
];
|
||||
$row = self::buildContactRow($externalContact, $followUsers);
|
||||
|
||||
self::upsertExternalContactRow($row, $syncCount, $newCount, $updateCount);
|
||||
|
||||
// 同步「客户↔员工↔标签」关系表(用于检索/统计;列表筛选/聚合不必再解析 follow_users JSON)
|
||||
self::syncContactTagsRelation((string) $row['external_userid'], $followUsers);
|
||||
|
||||
// 同步「客户↔员工」现存跟进关系表(企微口径的添加时间筛选用)
|
||||
self::syncContactFollowRelation((string) $row['external_userid'], $followUsers);
|
||||
|
||||
if (($syncCount % 25) === 0) {
|
||||
usleep(8000);
|
||||
}
|
||||
@@ -982,6 +970,67 @@ class CustomerLogic extends BaseLogic
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步「客户↔员工」现存跟进关系表(zyt_qywx_external_contact_follow)。
|
||||
*
|
||||
* 与企微后台「全部客户 + 时间筛选」同口径:只保留当前仍存在的跟进关系及其 createtime;
|
||||
* 关系被解除(员工删客户且未重加)后行随之删除,客户在对应日期的筛选下即不再出现。
|
||||
*
|
||||
* @param array<int, mixed> $followUsers /externalcontact/get 返回的 follow_user[]
|
||||
* @internal 仅供 UPSERT / 回填复用
|
||||
*/
|
||||
public static function syncContactFollowRelation(string $externalUserId, array $followUsers): void
|
||||
{
|
||||
$externalUserId = trim($externalUserId);
|
||||
if ($externalUserId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
foreach ($followUsers as $fu) {
|
||||
if (!is_array($fu)) {
|
||||
continue;
|
||||
}
|
||||
$uid = trim((string) ($fu['userid'] ?? ''));
|
||||
if ($uid === '') {
|
||||
continue;
|
||||
}
|
||||
$rows[$uid] = self::normalizeFollowUserCreatetime($fu);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($rows === []) {
|
||||
Db::name('qywx_external_contact_follow')
|
||||
->where('external_userid', $externalUserId)
|
||||
->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Db::name('qywx_external_contact_follow')
|
||||
->where('external_userid', $externalUserId)
|
||||
->whereNotIn('follow_user_id', array_keys($rows))
|
||||
->delete();
|
||||
|
||||
$now = time();
|
||||
foreach ($rows as $uid => $createtime) {
|
||||
Db::name('qywx_external_contact_follow')
|
||||
->duplicate(['createtime' => $createtime, 'update_time' => $now])
|
||||
->insert([
|
||||
'external_userid' => $externalUserId,
|
||||
'follow_user_id' => $uid,
|
||||
'createtime' => $createtime,
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('qywx contact follow relation sync failed: ' . $e->getMessage(), [
|
||||
'ext' => $externalUserId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 把所有 follow_user[].tags 合并去重(按 tag_id),返回 JSON 字符串。
|
||||
*
|
||||
@@ -1062,6 +1111,38 @@ class CustomerLogic extends BaseLogic
|
||||
return json_encode(array_values(array_unique($adminIds)), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 由企微 API 返回的 external_contact + follow_user 组装一行客户表数据
|
||||
*
|
||||
* @param array<string, mixed> $externalContact
|
||||
* @param array<int, array<string, mixed>> $followUsers
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function buildContactRow(array $externalContact, array $followUsers): array
|
||||
{
|
||||
$now = time();
|
||||
|
||||
return [
|
||||
'external_userid' => $externalContact['external_userid'] ?? '',
|
||||
'name' => $externalContact['name'] ?? '',
|
||||
'avatar' => $externalContact['avatar'] ?? '',
|
||||
'type' => $externalContact['type'] ?? 1,
|
||||
'gender' => $externalContact['gender'] ?? 0,
|
||||
'unionid' => $externalContact['unionid'] ?? '',
|
||||
'position' => $externalContact['position'] ?? '',
|
||||
'corp_name' => $externalContact['corp_name'] ?? '',
|
||||
'corp_full_name' => $externalContact['corp_full_name'] ?? '',
|
||||
'external_profile' => json_encode($externalContact['external_profile'] ?? [], JSON_UNESCAPED_UNICODE),
|
||||
'follow_users' => json_encode($followUsers, JSON_UNESCAPED_UNICODE),
|
||||
'tags' => self::extractFollowUserTags($followUsers),
|
||||
'follow_admin_ids' => self::resolveFollowAdminIds($followUsers),
|
||||
'external_first_add_time' => self::minFollowCreatetime($followUsers),
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
'delete_time' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否 MySQL 锁等待超时 / 死锁(可短重试)
|
||||
*/
|
||||
@@ -1073,7 +1154,13 @@ class CustomerLogic extends BaseLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* INSERT ... ON DUPLICATE KEY UPDATE + 锁冲突重试
|
||||
* 客户行写入 + 锁冲突重试。
|
||||
*
|
||||
* 表允许同一 external_userid 多行(重复添加会新增行),不再依赖唯一键 UPSERT:
|
||||
* - 已有行 → 只更新"最新一行"(MAX(id)),历史重加行保持原样;
|
||||
* - 无行 → 插入新行。
|
||||
* 更新时不覆盖 create_time / readd_flag / external_first_add_time
|
||||
* (最新行的添加时间代表"该行那次添加"的时间,由插入时决定,后续详情刷新不应改动)。
|
||||
*
|
||||
* @param array<string, mixed> $row
|
||||
*/
|
||||
@@ -1084,33 +1171,21 @@ class CustomerLogic extends BaseLogic
|
||||
while (true) {
|
||||
$attempt++;
|
||||
try {
|
||||
$affected = Db::name('qywx_external_contact')->duplicate([
|
||||
'name',
|
||||
'avatar',
|
||||
'type',
|
||||
'gender',
|
||||
'unionid',
|
||||
'position',
|
||||
'corp_name',
|
||||
'corp_full_name',
|
||||
'external_profile',
|
||||
'follow_users',
|
||||
'tags',
|
||||
'follow_admin_ids',
|
||||
'external_first_add_time',
|
||||
'update_time',
|
||||
'delete_time',
|
||||
])->insert($row);
|
||||
$latestId = (int) Db::name('qywx_external_contact')
|
||||
->where('external_userid', (string) $row['external_userid'])
|
||||
->order('id', 'desc')
|
||||
->value('id');
|
||||
|
||||
$syncCount++;
|
||||
// PDO MySQL:新插入 1;更新 2;值完全相同 0
|
||||
if ($affected === 1) {
|
||||
if ($latestId > 0) {
|
||||
$update = $row;
|
||||
unset($update['external_userid'], $update['create_time'], $update['external_first_add_time']);
|
||||
Db::name('qywx_external_contact')->where('id', $latestId)->update($update);
|
||||
$updateCount++;
|
||||
} else {
|
||||
Db::name('qywx_external_contact')->insert($row);
|
||||
$newCount++;
|
||||
} elseif ($affected === 2) {
|
||||
$updateCount++;
|
||||
} elseif ($affected === 0) {
|
||||
$updateCount++;
|
||||
}
|
||||
$syncCount++;
|
||||
|
||||
return;
|
||||
} catch (\Throwable $e) {
|
||||
@@ -1211,8 +1286,16 @@ class CustomerLogic extends BaseLogic
|
||||
*/
|
||||
public static function getStats()
|
||||
{
|
||||
$total = QywxExternalContact::whereNull('delete_time')->count();
|
||||
|
||||
// 表内同一客户可能多行(重复添加各占一行),去重后才是真实客户数
|
||||
$total = (int) Db::name('qywx_external_contact')
|
||||
->whereNull('delete_time')
|
||||
->fieldRaw('COUNT(DISTINCT external_userid) AS c')
|
||||
->find()['c'];
|
||||
|
||||
// 企微管理后台「全部客户」按"客户×添加人"关系计数(同一客户被 N 名员工添加计 N 条),
|
||||
// 现存跟进关系表与其同口径,便于对账。
|
||||
$relationTotal = (int) Db::name('qywx_external_contact_follow')->count();
|
||||
|
||||
$todayStart = strtotime(date('Y-m-d 00:00:00'));
|
||||
|
||||
// 今日进入数 = 今日收到的 add_external_contact 事件条数。
|
||||
@@ -1254,6 +1337,7 @@ class CustomerLogic extends BaseLogic
|
||||
|
||||
return [
|
||||
'total' => $total,
|
||||
'relation_total' => $relationTotal,
|
||||
'today' => $today,
|
||||
'today_follow_staff' => $todayFollowStaff,
|
||||
'lastSync' => $lastSync,
|
||||
@@ -1332,14 +1416,74 @@ class CustomerLogic extends BaseLogic
|
||||
}
|
||||
}
|
||||
|
||||
// 老客户回流数(去重客户维度):今天有 add 事件,但今天之前就加过——
|
||||
// 依据①事件流水里有更早的 add 事件,或②客户表首次添加时间早于今天(覆盖流水表上线前的老客户)
|
||||
$returning = 0;
|
||||
if ($total > 0) {
|
||||
$todayExtIds = Db::name('qywx_external_contact_event')
|
||||
->where('change_type', 'add_external_contact')
|
||||
->where('event_time', '>=', $todayStart)
|
||||
->where('event_time', '<', $todayEnd)
|
||||
->group('external_userid')
|
||||
->column('external_userid');
|
||||
$todayExtIds = array_values(array_filter($todayExtIds));
|
||||
if ($todayExtIds !== []) {
|
||||
$oldSet = self::resolveOldCustomerSet($todayExtIds, $todayStart);
|
||||
$returning = count($oldSet);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'total' => $total,
|
||||
'recent_time' => $recent,
|
||||
'returning' => $returning,
|
||||
'hourly' => array_values($hourly),
|
||||
'by_state' => $byState,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定"老客户":在 $beforeTs 之前就已加过企业的客户集合。
|
||||
*
|
||||
* 依据(满足其一即算老客户):
|
||||
* ① 事件流水表在 $beforeTs 之前存在该客户的 add_external_contact 记录;
|
||||
* ② 客户表 external_first_add_time(企微 follow_user 最早 createtime)早于 $beforeTs——
|
||||
* 覆盖事件流水表上线之前就已存在的客户。
|
||||
*
|
||||
* @param string[] $extIds
|
||||
* @return array<string, true> external_userid 为键的集合
|
||||
*/
|
||||
private static function resolveOldCustomerSet(array $extIds, int $beforeTs): array
|
||||
{
|
||||
$extIds = array_values(array_unique(array_filter($extIds)));
|
||||
if ($extIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$old = [];
|
||||
|
||||
$earlierEventIds = Db::name('qywx_external_contact_event')
|
||||
->whereIn('external_userid', $extIds)
|
||||
->where('change_type', 'add_external_contact')
|
||||
->where('event_time', '<', $beforeTs)
|
||||
->group('external_userid')
|
||||
->column('external_userid');
|
||||
foreach ($earlierEventIds as $id) {
|
||||
$old[(string) $id] = true;
|
||||
}
|
||||
|
||||
$earlierFirstAddIds = Db::name('qywx_external_contact')
|
||||
->whereIn('external_userid', $extIds)
|
||||
->where('external_first_add_time', '>', 0)
|
||||
->where('external_first_add_time', '<', $beforeTs)
|
||||
->column('external_userid');
|
||||
foreach ($earlierFirstAddIds as $id) {
|
||||
$old[(string) $id] = true;
|
||||
}
|
||||
|
||||
return $old;
|
||||
}
|
||||
|
||||
/**
|
||||
* 今日"进入明细"流水(分页,按时间倒序)
|
||||
*
|
||||
@@ -1391,6 +1535,9 @@ class CustomerLogic extends BaseLogic
|
||||
$adminMap = Admin::whereIn('work_wechat_userid', $userIds)->column('name', 'work_wechat_userid');
|
||||
}
|
||||
|
||||
// 标记老客户:今天之前就加过企业(重加 / 被另一名员工添加的回流客户)
|
||||
$oldSet = self::resolveOldCustomerSet($extIds, $todayStart);
|
||||
|
||||
$lists = [];
|
||||
foreach ($rows as $r) {
|
||||
$ext = (string) ($r['external_userid'] ?? '');
|
||||
@@ -1405,6 +1552,7 @@ class CustomerLogic extends BaseLogic
|
||||
'customer_avatar' => (string) ($customerMap[$ext]['avatar'] ?? ''),
|
||||
'state' => (string) ($r['state'] ?? ''),
|
||||
'welcome_code' => (int) ($r['welcome_code'] ?? 0),
|
||||
'is_old_customer' => isset($oldSet[$ext]) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\stats;
|
||||
|
||||
use app\adminapi\logic\dept\DeptLogic;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
@@ -12,9 +13,11 @@ use think\facade\Db;
|
||||
* 口径说明:
|
||||
* - 当月被指派总数 = 当月内 `tcm_diagnosis_assign_log`(按 **指派操作时间 lg.create_time** 落月,to_assistant_id>0,
|
||||
* **剔除勾选「继承」的指派 is_inherit=1**,诊单未删除)去重后的「医助 × 诊单」组合;
|
||||
* 同一诊单当月被多次指派给同一医助只计 1 次。
|
||||
* 同一诊单当月被多次指派给同一医助只计 1 次;
|
||||
* **再剔除**名下存在履约「拒收(9) / 退款(10)」业务订单(未软删)的诊单。
|
||||
* - 第 N 次下单 = 诊单(患者)名下计入业绩的业务订单(剔除履约 4/9/10、软删)按 create_time 升序的**全局**序列中第 N 笔;
|
||||
* 诊次**跨月累计不重置**:例如 5 月指派后旗下成交 4 单为二诊~五诊,6 月再成交即为六诊。
|
||||
* 诊单可配置 `revisit_slot_start_offset`(默认 0:第 1 笔实单计为一诊;设为 1 则第 1 笔实单计为二诊;设为 2 则计为三诊,即在实单序号上叠加偏移,5 笔实单+偏移 2 等价于计至七诊)。
|
||||
* - 当月 N 诊单数 = **当月内下单**且全局序号为 N 的订单数,归属下单时点的**持有医助**——
|
||||
* 按指派日志时间线取「订单时间之前最近一次指派」的 to_assistant_id(释放 to=0 即不再归属;
|
||||
* 「继承」指派会转移持有人用于归属,但不计被指派数)。指派可发生在往月。
|
||||
@@ -22,7 +25,8 @@ use think\facade\Db;
|
||||
* 医助当月无新指派但旗下有成交时,被指派数为 0、比率显示为空。
|
||||
* - 分档动态产出:N 从 2 起,至当月命中数据的最大序号(至少展示到四诊,上限 MAX_VISIT_SLOT 防御异常数据),
|
||||
* 返回 `slots` 列表供前端动态渲染「五诊」「六诊」… 列。
|
||||
* - 部门归类:医助按其人事部门(admin_dept 最小 dept_id)归组;部门筛选(dept_ids,含组织下级)按该归属部门过滤。
|
||||
* - 部门归类:医助按其人事部门(admin_dept 最小 dept_id)归组;**仅统计「二中心」及其组织下级**(与 DeptLogic::getErCenterSubtreeDeptIdSet 一致);
|
||||
* 部门筛选下拉与未选部门时的默认范围均限定在该子树内,选定部门时含其组织下级。
|
||||
* - 部门行 / 合计行:被指派数按诊单去重(可能小于下级行相加);N 诊单数为下级行求和(每笔订单唯一归属一名医助)。
|
||||
*/
|
||||
class RevisitRateLogic
|
||||
@@ -294,14 +298,19 @@ class RevisitRateLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门下拉(全量未删除部门,前端组树)。
|
||||
* 部门下拉:仅「二中心」及其组织下级(与业绩看板 ErCenter 子树一致)。
|
||||
*
|
||||
* @return array{rows: list<array{id:int,pid:int,name:string}>}
|
||||
*/
|
||||
public static function deptOptions(): array
|
||||
{
|
||||
$erSet = DeptLogic::getErCenterSubtreeDeptIdSet();
|
||||
if ($erSet === []) {
|
||||
return ['rows' => []];
|
||||
}
|
||||
$rows = Db::name('dept')
|
||||
->whereNull('delete_time')
|
||||
->whereIn('id', array_keys($erSet))
|
||||
->field(['id', 'pid', 'name'])
|
||||
->order('sort', 'desc')
|
||||
->order('id', 'asc')
|
||||
@@ -322,9 +331,9 @@ class RevisitRateLogic
|
||||
/**
|
||||
* 核心统计上下文:
|
||||
* 1. 全量指派日志(≤ 月末)构建持有时间线;
|
||||
* 2. 分母:当月非继承指派的「医助 × 诊单」;
|
||||
* 3. 分子:曾被指派诊单的当月订单按全局序号 ≥2 归属持有医助;
|
||||
* 4. 应用部门筛选(含组织下级)。
|
||||
* 2. 分母:当月非继承指派的「医助 × 诊单」,再剔除名下存在拒收(9)/退款(10) 订单的诊单;
|
||||
* 3. 分子:曾被指派诊单的当月订单,统计诊次 = 实单序号 + 诊单偏移(默认第 1 笔实单为一诊);
|
||||
* 4. 应用部门筛选:默认限定「二中心」子树;选定部门时再收窄到该部门及其下级(且须落在二中心子树内)。
|
||||
*
|
||||
* @param array{month?:string,dept_ids?:int[]|string} $params
|
||||
*
|
||||
@@ -375,9 +384,35 @@ class RevisitRateLogic
|
||||
}
|
||||
}
|
||||
|
||||
// 分子:曾被指派诊单的当月订单(全局序号 ≥2),归属下单时点的持有医助
|
||||
// 分母:剔除名下存在拒收(9)/退款(10) 业务订单的诊单(与明细 assignLines 同口径)
|
||||
$assignedDiagIds = [];
|
||||
foreach ($diagsByAssistant as $diagSet) {
|
||||
foreach ($diagSet as $did => $_) {
|
||||
$assignedDiagIds[(int) $did] = true;
|
||||
}
|
||||
}
|
||||
$refundRejectDiagSet = self::fetchRefundOrRejectDiagnosisSet(array_keys($assignedDiagIds));
|
||||
if ($refundRejectDiagSet !== []) {
|
||||
foreach ($diagsByAssistant as $aid => $diagSet) {
|
||||
foreach ($diagSet as $did => $_) {
|
||||
if (isset($refundRejectDiagSet[$did])) {
|
||||
unset($diagsByAssistant[$aid][$did]);
|
||||
}
|
||||
}
|
||||
if ($diagsByAssistant[$aid] === []) {
|
||||
unset($diagsByAssistant[$aid]);
|
||||
}
|
||||
}
|
||||
$pairsRaw = array_values(array_filter(
|
||||
$pairsRaw,
|
||||
static fn (array $p): bool => !isset($refundRejectDiagSet[(int) $p['diagnosis_id']])
|
||||
));
|
||||
}
|
||||
|
||||
// 分子:曾被指派诊单的当月订单,统计诊次 = 实单全局序号 + 诊单偏移(默认偏移 0 → 第 1 笔实单为一诊)
|
||||
/** @var array<int, array<int, list<array<string, mixed>>>> $slotOrdersByAssistant */
|
||||
$slotOrdersByAssistant = [];
|
||||
$offsetMap = self::fetchRevisitSlotStartOffsetMap(array_keys($candidateDiagSet));
|
||||
foreach (array_chunk(array_keys($candidateDiagSet), 2000) as $chunk) {
|
||||
$orderRows = self::fetchOrderSeqRows(
|
||||
$chunk,
|
||||
@@ -387,6 +422,7 @@ class RevisitRateLogic
|
||||
$seq = 0;
|
||||
$ptr = 0;
|
||||
$holder = 0;
|
||||
$offset = 0;
|
||||
foreach ($orderRows as $r) {
|
||||
$did = (int) ($r['diagnosis_id'] ?? 0);
|
||||
if ($did <= 0) {
|
||||
@@ -397,8 +433,10 @@ class RevisitRateLogic
|
||||
$seq = 0;
|
||||
$ptr = 0;
|
||||
$holder = 0;
|
||||
$offset = self::resolveRevisitSlotStartOffset($did, $offsetMap);
|
||||
}
|
||||
$seq++;
|
||||
$effectiveSlot = $seq + $offset;
|
||||
$ct = (int) ($r['create_time'] ?? 0);
|
||||
// 推进时间线指针:订单时间之前(含同刻)最近一次指派的持有人
|
||||
$tl = $timeline[$did] ?? [];
|
||||
@@ -407,24 +445,27 @@ class RevisitRateLogic
|
||||
$holder = (int) $tl[$ptr]['to'];
|
||||
$ptr++;
|
||||
}
|
||||
if ($seq < 2 || $seq > self::MAX_VISIT_SLOT) {
|
||||
if ($effectiveSlot < 2 || $effectiveSlot > self::MAX_VISIT_SLOT) {
|
||||
continue;
|
||||
}
|
||||
if ($ct < $startTs || $ct > $endTs) {
|
||||
continue;
|
||||
}
|
||||
if ($holder > 0) {
|
||||
$slotOrdersByAssistant[$holder][$seq][] = $r;
|
||||
$slotOrdersByAssistant[$holder][$effectiveSlot][] = $r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 医助归属部门 + 部门筛选(含组织下级)
|
||||
// 医助归属部门 + 部门筛选(默认仅二中心子树;选定部门时再收窄,含组织下级)
|
||||
$universeIds = array_keys($diagsByAssistant + $slotOrdersByAssistant);
|
||||
[$assistantDept, $deptNames] = self::buildAssistantDeptIndex($universeIds);
|
||||
$deptFilterIds = self::parseDeptIds($params['dept_ids'] ?? null);
|
||||
if ($deptFilterIds !== []) {
|
||||
$subtreeSet = self::expandDeptSubtreeSet($deptFilterIds);
|
||||
$subtreeSet = self::resolveDeptFilterSet($params['dept_ids'] ?? null);
|
||||
if ($subtreeSet === []) {
|
||||
// 无二中心部门时整表为空,避免误展示其它中心数据
|
||||
$diagsByAssistant = [];
|
||||
$slotOrdersByAssistant = [];
|
||||
} else {
|
||||
foreach ($universeIds as $aid) {
|
||||
$deptId = (int) ($assistantDept[$aid] ?? 0);
|
||||
if ($deptId <= 0 || !isset($subtreeSet[$deptId])) {
|
||||
@@ -543,6 +584,45 @@ class RevisitRateLogic
|
||||
return [$canonical, $names];
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门筛选集合:始终落在「二中心」子树内。
|
||||
* - 未传 dept_ids:整棵二中心子树
|
||||
* - 已传:所选部门及其下级 ∩ 二中心子树(非法/非二中心 id 被忽略)
|
||||
*
|
||||
* @param mixed $raw
|
||||
*
|
||||
* @return array<int, true>
|
||||
*/
|
||||
private static function resolveDeptFilterSet(mixed $raw): array
|
||||
{
|
||||
$erSet = DeptLogic::getErCenterSubtreeDeptIdSet();
|
||||
if ($erSet === []) {
|
||||
return [];
|
||||
}
|
||||
$deptFilterIds = self::parseDeptIds($raw);
|
||||
if ($deptFilterIds === []) {
|
||||
return $erSet;
|
||||
}
|
||||
$allowedRoots = [];
|
||||
foreach ($deptFilterIds as $id) {
|
||||
if (isset($erSet[$id])) {
|
||||
$allowedRoots[] = $id;
|
||||
}
|
||||
}
|
||||
if ($allowedRoots === []) {
|
||||
return [];
|
||||
}
|
||||
$expanded = self::expandDeptSubtreeSet($allowedRoots);
|
||||
$out = [];
|
||||
foreach ($expanded as $id => $_) {
|
||||
if (isset($erSet[$id])) {
|
||||
$out[$id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw int[] | 逗号分隔字符串
|
||||
*
|
||||
@@ -596,6 +676,35 @@ class RevisitRateLogic
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* 名下存在履约「拒收(9) / 退款(10)」业务订单(未软删)的诊单集合。
|
||||
* 用于「当月被指派总数」分母过滤;不限订单创建月份。
|
||||
*
|
||||
* @param list<int> $diagIds
|
||||
*
|
||||
* @return array<int, true>
|
||||
*/
|
||||
private static function fetchRefundOrRejectDiagnosisSet(array $diagIds): array
|
||||
{
|
||||
if ($diagIds === []) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach (array_chunk($diagIds, 2000) as $chunk) {
|
||||
$ids = Db::name('tcm_prescription_order')
|
||||
->whereIn('diagnosis_id', $chunk)
|
||||
->whereNull('delete_time')
|
||||
->whereIn('fulfillment_status', [9, 10])
|
||||
->group('diagnosis_id')
|
||||
->column('diagnosis_id');
|
||||
foreach ($ids as $id) {
|
||||
$out[(int) $id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊单订单序列源查询(与业绩口径一致),统一排序保证序号稳定。
|
||||
*
|
||||
@@ -619,6 +728,48 @@ class RevisitRateLogic
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $diagIds
|
||||
*
|
||||
* @return array<int, int> diagnosis_id => revisit_slot_start_offset
|
||||
*/
|
||||
private static function fetchRevisitSlotStartOffsetMap(array $diagIds): array
|
||||
{
|
||||
if ($diagIds === []) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach (array_chunk($diagIds, 2000) as $chunk) {
|
||||
$rows = Db::name('tcm_diagnosis')
|
||||
->whereIn('id', $chunk)
|
||||
->whereNull('delete_time')
|
||||
->column('revisit_slot_start_offset', 'id');
|
||||
foreach ($rows as $id => $offset) {
|
||||
$out[(int) $id] = (int) $offset;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊单复诊统计起始偏移(默认 0:第 1 笔实单计为一诊;统计诊次 = 实单序号 + 偏移)
|
||||
*
|
||||
* @param array<int, int> $offsetMap
|
||||
*/
|
||||
private static function resolveRevisitSlotStartOffset(int $diagId, array $offsetMap): int
|
||||
{
|
||||
$offset = (int) ($offsetMap[$diagId] ?? 0);
|
||||
if ($offset < 0) {
|
||||
$offset = 0;
|
||||
}
|
||||
if ($offset > 20) {
|
||||
$offset = 20;
|
||||
}
|
||||
|
||||
return $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $diagIds
|
||||
*
|
||||
|
||||
@@ -748,6 +748,49 @@ class DiagnosisLogic extends BaseLogic
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 诊单指派医助操作记录列表
|
||||
*/
|
||||
public static function setRevisitSlotStartOffset(int $diagnosisId, int $offset, array $adminInfo): bool
|
||||
{
|
||||
self::$error = '';
|
||||
if ($diagnosisId <= 0) {
|
||||
self::setError('诊单不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($offset < 0 || $offset > 20) {
|
||||
self::setError('起始偏移须在 0~20 之间');
|
||||
|
||||
return false;
|
||||
}
|
||||
$diagnosis = Diagnosis::where('id', $diagnosisId)->whereNull('delete_time')->find();
|
||||
if (!$diagnosis) {
|
||||
self::setError('诊单不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
$old = (int) ($diagnosis->revisit_slot_start_offset ?? 0);
|
||||
if ($old < 0) {
|
||||
$old = 0;
|
||||
}
|
||||
if ($old > 20) {
|
||||
$old = 20;
|
||||
}
|
||||
if ($old === $offset) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
$diagnosis->save(['revisit_slot_start_offset' => $offset]);
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 诊单指派医助操作记录列表
|
||||
*/
|
||||
|
||||
@@ -3407,17 +3407,14 @@ class PrescriptionOrderLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用:主方/辅方服用天数(优先业务订单 medication_days,缺省回退处方 usage_days / 辅方 aux_usage)
|
||||
* 导出用:主方/辅方开立天数(与详情侧栏「处方开立」同口径:主方取处方 usage_days,辅方取 aux_usage.usage_days)
|
||||
* 订单服用天数单独导出在 export_medication_days 列,不在此混用。
|
||||
*
|
||||
* @param array<string, mixed> $rx
|
||||
* @param array<string, mixed>|null $auxUsage
|
||||
*/
|
||||
private static function resolveExportUsageDays(array $rx, ?array $auxUsage, $orderMedicationDays, bool $isAux): string
|
||||
private static function resolveExportUsageDays(array $rx, ?array $auxUsage, bool $isAux): string
|
||||
{
|
||||
$medDays = $orderMedicationDays;
|
||||
if ($medDays !== null && $medDays !== '' && (int) $medDays > 0) {
|
||||
return (string) (int) $medDays;
|
||||
}
|
||||
if ($isAux) {
|
||||
$days = (int) ($auxUsage['usage_days'] ?? 0);
|
||||
|
||||
@@ -3990,10 +3987,9 @@ class PrescriptionOrderLogic
|
||||
} else {
|
||||
$item['export_aux_usage'] = '';
|
||||
}
|
||||
$orderMedDays = $item['medication_days'] ?? null;
|
||||
$item['export_main_usage_days'] = self::resolveExportUsageDays($rxArr, $auxUsageNorm, $orderMedDays, false);
|
||||
$item['export_main_usage_days'] = self::resolveExportUsageDays($rxArr, $auxUsageNorm, false);
|
||||
$item['export_aux_usage_days'] = $auxHerbs !== []
|
||||
? self::resolveExportUsageDays($rxArr, $auxUsageNorm, $orderMedDays, true)
|
||||
? self::resolveExportUsageDays($rxArr, $auxUsageNorm, true)
|
||||
: '';
|
||||
|
||||
$item['export_service_package'] = self::formatServicePackageForExport(
|
||||
@@ -4885,4 +4881,175 @@ class PrescriptionOrderLogic
|
||||
{
|
||||
self::writeLog($prescriptionOrderId, $adminId, $adminInfo, 'patch_rx_patient', $summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务订单详情场景:更新主方/辅方服用次数与开立天数,以及订单服用天数
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public static function patchPrescriptionUsage(array $params, int $adminId, array $adminInfo): bool
|
||||
{
|
||||
self::$error = '';
|
||||
$prescriptionOrderId = (int) ($params['id'] ?? 0);
|
||||
$order = PrescriptionOrder::where('id', $prescriptionOrderId)->whereNull('delete_time')->find();
|
||||
if (!$order) {
|
||||
self::setError('订单不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!self::canAccessOrder($order, $adminId, $adminInfo)) {
|
||||
self::setError('无权限操作');
|
||||
|
||||
return false;
|
||||
}
|
||||
if ((int) $order->fulfillment_status === 4) {
|
||||
self::setError('已取消的订单不可修改');
|
||||
|
||||
return false;
|
||||
}
|
||||
$rxId = (int) ($order->prescription_id ?? 0);
|
||||
if ($rxId <= 0) {
|
||||
self::setError('该订单未关联处方');
|
||||
|
||||
return false;
|
||||
}
|
||||
$rx = Prescription::where('id', $rxId)->whereNull('delete_time')->find();
|
||||
if (!$rx) {
|
||||
self::setError('处方不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!PrescriptionLogic::canViewPrescription($rx, $adminId, $adminInfo)) {
|
||||
self::setError('无权限修改此处方');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$timesPerDay = (int) ($params['times_per_day'] ?? 0);
|
||||
$usageDays = (int) ($params['usage_days'] ?? 0);
|
||||
$medDays = (int) ($params['medication_days'] ?? 0);
|
||||
if ($timesPerDay < 1 || $timesPerDay > 6) {
|
||||
self::setError('主方每天次数须在 1~6 之间');
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($usageDays < 1 || $usageDays > 999) {
|
||||
self::setError('主方开立天数须在 1~999 之间');
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($medDays < 1 || $medDays > 999) {
|
||||
self::setError('订单服用天数须在 1~999 之间');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$hasAux = self::prescriptionHasAuxFormula($rx);
|
||||
$auxTimesPerDay = null;
|
||||
$auxUsageDays = null;
|
||||
if ($hasAux) {
|
||||
if (!array_key_exists('aux_times_per_day', $params) || !array_key_exists('aux_usage_days', $params)) {
|
||||
self::setError('含辅方处方须填写辅方服用参数');
|
||||
|
||||
return false;
|
||||
}
|
||||
$auxTimesPerDay = (int) $params['aux_times_per_day'];
|
||||
$auxUsageDays = (int) $params['aux_usage_days'];
|
||||
if ($auxTimesPerDay < 1 || $auxTimesPerDay > 6) {
|
||||
self::setError('辅方每天次数须在 1~6 之间');
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($auxUsageDays < 1 || $auxUsageDays > 999) {
|
||||
self::setError('辅方开立天数须在 1~999 之间');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$oldTimes = (int) ($rx->times_per_day ?? 0);
|
||||
$oldUsageDays = (int) ($rx->usage_days ?? 0);
|
||||
$oldMedDays = (int) ($order->medication_days ?? 0);
|
||||
$oldAuxUsage = $rx->aux_usage;
|
||||
if (is_string($oldAuxUsage)) {
|
||||
$decoded = json_decode($oldAuxUsage, true);
|
||||
$oldAuxUsage = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($oldAuxUsage)) {
|
||||
$oldAuxUsage = [];
|
||||
}
|
||||
$oldAuxTimes = (int) ($oldAuxUsage['times_per_day'] ?? 0);
|
||||
$oldAuxUsageDays = (int) ($oldAuxUsage['usage_days'] ?? 0);
|
||||
|
||||
try {
|
||||
$rxUpdates = [
|
||||
'times_per_day' => $timesPerDay,
|
||||
'usage_days' => $usageDays,
|
||||
];
|
||||
if ($hasAux) {
|
||||
$auxUsage = $oldAuxUsage;
|
||||
$auxUsage['times_per_day'] = $auxTimesPerDay;
|
||||
$auxUsage['usage_days'] = $auxUsageDays;
|
||||
$rxUpdates['aux_usage'] = $auxUsage;
|
||||
}
|
||||
$rx->save($rxUpdates);
|
||||
|
||||
$order->medication_days = $medDays;
|
||||
$order->save();
|
||||
|
||||
$parts = [
|
||||
sprintf(
|
||||
'主方 每天%d次/开立%d天 → 每天%d次/开立%d天',
|
||||
$oldTimes > 0 ? $oldTimes : 0,
|
||||
$oldUsageDays > 0 ? $oldUsageDays : 0,
|
||||
$timesPerDay,
|
||||
$usageDays
|
||||
),
|
||||
];
|
||||
if ($hasAux) {
|
||||
$parts[] = sprintf(
|
||||
'辅方 每天%d次/开立%d天 → 每天%d次/开立%d天',
|
||||
$oldAuxTimes > 0 ? $oldAuxTimes : 0,
|
||||
$oldAuxUsageDays > 0 ? $oldAuxUsageDays : 0,
|
||||
$auxTimesPerDay,
|
||||
$auxUsageDays
|
||||
);
|
||||
}
|
||||
$parts[] = sprintf(
|
||||
'订单设置 %d天 → %d天',
|
||||
$oldMedDays > 0 ? $oldMedDays : 0,
|
||||
$medDays
|
||||
);
|
||||
$summary = '服用参数:' . implode(';', $parts);
|
||||
self::writeLog($prescriptionOrderId, $adminId, $adminInfo, 'patch_rx_usage', $summary);
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处方是否含辅方药材(与列表/详情 has_aux_formula 口径一致)
|
||||
*/
|
||||
private static function prescriptionHasAuxFormula(Prescription $rx): bool
|
||||
{
|
||||
$herbs = $rx->herbs;
|
||||
if (is_string($herbs)) {
|
||||
$decoded = json_decode($herbs, true);
|
||||
$herbs = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($herbs)) {
|
||||
return false;
|
||||
}
|
||||
foreach ($herbs as $h) {
|
||||
if (is_array($h) && (string) ($h['formula_type'] ?? '') === '辅方') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ class DiagnosisValidate extends BaseValidate
|
||||
'end_date' => 'date|checkDateRange',
|
||||
'diagnosis_id' => 'require|integer|checkDiagnosisId',
|
||||
'tracking_content' => 'require|length:1,1000',
|
||||
'revisit_slot_start_offset' => 'integer|between:0,20',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
@@ -139,6 +140,13 @@ class DiagnosisValidate extends BaseValidate
|
||||
return $this->only(['id']);
|
||||
}
|
||||
|
||||
/** 业务订单 tab:设置复诊接诊率统计起始偏移 */
|
||||
public function sceneSetRevisitSlotStartOffset()
|
||||
{
|
||||
return $this->only(['id', 'revisit_slot_start_offset'])
|
||||
->append('revisit_slot_start_offset', 'require|integer|between:0,20');
|
||||
}
|
||||
|
||||
protected function checkDiagnosis($value)
|
||||
{
|
||||
$diagnosis = Diagnosis::findOrEmpty($value);
|
||||
|
||||
@@ -89,6 +89,7 @@ class PrescriptionOrderValidate extends BaseValidate
|
||||
'submitGancaoRecipel' => ['id'],
|
||||
'previewGancaoRecipel' => ['id'],
|
||||
'patchPrescriptionPatient' => ['id', 'patient_name', 'phone'],
|
||||
'patchPrescriptionUsage' => ['id', 'times_per_day', 'usage_days', 'medication_days', 'aux_times_per_day', 'aux_usage_days'],
|
||||
'updateAmount' => ['id', 'amount'],
|
||||
'setShipMode' => ['id', 'ship_mode'],
|
||||
];
|
||||
@@ -99,4 +100,15 @@ class PrescriptionOrderValidate extends BaseValidate
|
||||
->append('id', 'require|integer|gt:0')
|
||||
->append('amount', 'require|float|egt:0');
|
||||
}
|
||||
|
||||
public function patchPrescriptionUsage(): PrescriptionOrderValidate
|
||||
{
|
||||
return $this->only(['id', 'times_per_day', 'usage_days', 'medication_days', 'aux_times_per_day', 'aux_usage_days'])
|
||||
->append('id', 'require|integer|gt:0')
|
||||
->append('times_per_day', 'require|integer|between:1,6')
|
||||
->append('usage_days', 'require|integer|between:1,999')
|
||||
->append('medication_days', 'require|integer|between:1,999')
|
||||
->append('aux_times_per_day', 'integer|between:1,6')
|
||||
->append('aux_usage_days', 'integer|between:1,999');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,12 +133,6 @@ class QywxExternalContactCallbackController extends BaseApiController
|
||||
$failReason !== '' ? $failReason : '-'
|
||||
));
|
||||
|
||||
if ($changeType === 'del_external_contact') {
|
||||
CustomerLogic::softDeleteExternalContactRow($extId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($changeType === 'add_half_external_contact') {
|
||||
// 半客户:客户尚未通过验证,/externalcontact/get 通常返回 84061「客户尚未通过」之类,
|
||||
// 这里只 log 不写库,避免产生 noise;客户通过后会再触发 add_external_contact 事件再走 UPSERT。
|
||||
@@ -153,17 +147,17 @@ class QywxExternalContactCallbackController extends BaseApiController
|
||||
return;
|
||||
}
|
||||
|
||||
if ($changeType === 'del_follow_user') {
|
||||
// 某员工不再跟进该客户:只需把本地 follow_users 里对应 userid 移除;
|
||||
// 若已无跟进人则软删;不再回调 /externalcontact/get(最后一个跟进人被删时会稳定返回 84061)。
|
||||
if ($userId !== '') {
|
||||
CustomerLogic::removeFollowUserFromLocal($extId, $userId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 其余变更(添加/编辑/转接成功/标签变化等):以 get 详情为准 UPSERT,避免遗漏未枚举的 ChangeType
|
||||
CustomerLogic::upsertSingleExternalContactFromApi($extId);
|
||||
// 其余全部变更(添加/编辑/删除/被删/转接/标签变化等):一律以 /externalcontact/get 的真实状态为准。
|
||||
// - 仍是企业客户(含"客户单方删除员工",企微会保留客户与跟进关系)→ UPSERT 最新数据;
|
||||
// - 已彻底不是企业客户(84061)→ 本地软删。
|
||||
// 实测验证:del_follow_user(客户删员工)后 get 依然成功返回且 follow_user 保留,
|
||||
// 本地自行推断移除跟进人/软删会导致客户数持续少于企微,故删除类事件不再走本地推断。
|
||||
// 添加事件若命中已有客户(含软删行):不动原有行,新增一行 readd_flag=1(以前加过)的记录。
|
||||
CustomerLogic::upsertSingleExternalContactFromApi(
|
||||
$extId,
|
||||
$changeType === 'add_external_contact',
|
||||
$userId,
|
||||
$eventTime
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import t from"./error-D-UZdrhy.js";import{o,q as r,r as a,v as n,D as c,s}from"./.pnpm-BGZW0UGg.js";import"./index-CeIwrh_6.js";const p="/admin/assets/no_perms-jDxcYpYC.png",i={class:"error404"},x=o({__name:"403",setup(m){return(_,e)=>(r(),a("div",i,[n(t,{code:"403",title:"您的账号权限不足,请联系管理员添加权限!","show-btn":!1},{content:c(()=>[...e[0]||(e[0]=[s("div",{class:"flex justify-center"},[s("img",{class:"w-[150px] h-[150px]",src:p,alt:""})],-1)])]),_:1})]))}});export{x as default};
|
||||
@@ -0,0 +1 @@
|
||||
import r from"./error-DFSD5l9g.js";import{f as p,ak as i,I as m,a as e,aN as s,J as o}from"./@vue/runtime-core-C6bnekPw.js";import"./element-plus-Bolc0EfP.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./@vue/shared-mAAVTE9n.js";import"./@vue/reactivity-DiY1c2vO.js";import"./@element-plus/icons-vue-B0jSCQ-G.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./vue-router-QlpZ4wdW.js";import"./index-d3j0BX4t.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const a="/admin/assets/no_perms-jDxcYpYC.png",n={class:"error404"},W=p({__name:"403",setup(c){return(_,t)=>(i(),m("div",n,[e(r,{code:"403",title:"您的账号权限不足,请联系管理员添加权限!","show-btn":!1},{content:s(()=>[...t[0]||(t[0]=[o("div",{class:"flex justify-center"},[o("img",{class:"w-[150px] h-[150px]",src:a,alt:""})],-1)])]),_:1})]))}});export{W as default};
|
||||
@@ -1 +0,0 @@
|
||||
import e from"./error-D-UZdrhy.js";import{o,q as r,r as t,v as s}from"./.pnpm-BGZW0UGg.js";import"./index-CeIwrh_6.js";const a={class:"error404"},d=o({__name:"404",setup(c){return(n,_)=>(r(),t("div",a,[s(e,{code:"404",title:"哎呀,出错了!您访问的页面不存在…"})]))}});export{d as default};
|
||||
@@ -0,0 +1 @@
|
||||
import o from"./error-DFSD5l9g.js";import{f as r,ak as t,I as m,a as p}from"./@vue/runtime-core-C6bnekPw.js";import"./element-plus-Bolc0EfP.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./@vue/shared-mAAVTE9n.js";import"./@vue/reactivity-DiY1c2vO.js";import"./@element-plus/icons-vue-B0jSCQ-G.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./vue-router-QlpZ4wdW.js";import"./index-d3j0BX4t.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const i={class:"error404"},T=r({__name:"404",setup(e){return(a,s)=>(t(),m("div",i,[p(o,{code:"404",title:"哎呀,出错了!您访问的页面不存在…"})]))}});export{T as default};
|
||||
@@ -0,0 +1 @@
|
||||
function a(e){"@babel/helpers - typeof";return a=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},a(e)}function c(e,t,r,n,f,y,i){try{var u=e[y](i),o=u.value}catch(l){return void r(l)}u.done?t(o):Promise.resolve(o).then(n,f)}function p(e){return function(){var t=this,r=arguments;return new Promise(function(n,f){var y=e.apply(t,r);function i(o){c(y,n,f,i,u,"next",o)}function u(o){c(y,n,f,i,u,"throw",o)}i(void 0)})}}function b(e,t){if(a(e)!="object"||!e)return e;var r=e[Symbol.toPrimitive];if(r!==void 0){var n=r.call(e,t);if(a(n)!="object")return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function m(e){var t=b(e,"string");return a(t)=="symbol"?t:t+""}function s(e,t,r){return(t=m(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}export{a as _,p as a,s as b};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import{H as u}from"../highlight.js-Bxt7hFFy.js";import{f as c,i as g,w as s,A as n}from"../@vue/runtime-core-C6bnekPw.js";import{n as h}from"../@vue/reactivity-DiY1c2vO.js";var i=c({props:{code:{type:String,required:!0},language:{type:String,default:""},autodetect:{type:Boolean,default:!0},ignoreIllegals:{type:Boolean,default:!0}},setup:function(e){var t=h(e.language);s((function(){return e.language}),(function(a){t.value=a}));var r=n((function(){return e.autodetect||!t.value})),o=n((function(){return!r.value&&!u.getLanguage(t.value)}));return{className:n((function(){return o.value?"":"hljs "+t.value})),highlightedCode:n((function(){var a;if(o.value)return console.warn('The language "'+t.value+'" you specified could not be found.'),e.code.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'");if(r.value){var l=u.highlightAuto(e.code);return t.value=(a=l.language)!==null&&a!==void 0?a:"",l.value}return(l=u.highlight(e.code,{language:t.value,ignoreIllegals:e.ignoreIllegals})).value}))}},render:function(){return g("pre",{},[g("code",{class:this.className,innerHTML:this.highlightedCode})])}}),v={install:function(e){e.component("highlightjs",i)},component:i};export{v as o};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import"./uikit-base-component-vue3-YgTqL4da.js";import{A as r}from"../tuikit-atomicx-vue3-Dln8Zi6e.js";import{f as s,ak as c,I as l,aq as m,G as u,at as i,H as p,A as d}from"../@vue/runtime-core-C6bnekPw.js";import{y as v}from"../@vue/reactivity-DiY1c2vO.js";import"./chat-uikit-engine-zx802ozq.js";const _=(t,o)=>{const a=t.__vccOpts||t;for(const[e,n]of o)a[e]=n;return a},f={key:0,class:"chat"},h=s({name:"Chat",__name:"Chat",props:{PlaceholderEmpty:{default:null}},setup(t){const{activeConversation:o}=r(),a=d(()=>{var e;return!((e=o.value)!=null&&e.conversationID)});return(e,n)=>v(o)?(c(),l("div",f,[m(e.$slots,"default",{},void 0,!0)])):a.value&&t.PlaceholderEmpty?(c(),u(i(t.PlaceholderEmpty),{key:1})):p("",!0)}}),A=_(h,[["__scopeId","data-v-1c9c77cd"]]);typeof window<"u"&&(window.__CHAT_ATOMICX_VUE3__={name:"@tencentcloud/chat-uikit-vue3",version:"4.5.4"},console.log("[@tencentcloud/chat-uikit-vue3] v4.5.4"));export{A as E};
|
||||
@@ -0,0 +1 @@
|
||||
.chat[data-v-1c9c77cd]{display:flex;flex-direction:column;min-width:0}.uikit-chat-header[data-v-a0c42ddc]{padding:14px 10px;height:64px;display:flex;justify-content:center;background-color:var(--bg-color-operate)}.uikit-chat-header__container[data-v-a0c42ddc]{padding:0 10px;flex-direction:row;align-items:center;justify-content:space-between}.uikit-chat-header__left[data-v-a0c42ddc]{flex:1 1 auto;display:flex;flex-direction:row;align-items:center}.uikit-chat-header__avatar[data-v-a0c42ddc]{margin-right:12px}.uikit-chat-header__info[data-v-a0c42ddc]{flex:1;display:flex;flex-direction:column;justify-content:center}.uikit-chat-header__title[data-v-a0c42ddc]{display:block;margin:0;font-size:16px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-color-primary)}.uikit-chat-header__typing-indicator[data-v-a0c42ddc]{font-size:12px;color:var(--text-color-secondary)}.uikit-chat-header__live[data-v-a0c42ddc]{margin-top:4px;font-size:12px;color:var(--text-color-secondary)}/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}:root{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*,*:after,*:before{box-sizing:border-box}ul,li{list-style:none;padding:0;margin:0}picture,img,video,canvas,svg{display:block;max-width:100%}img{max-width:100%;height:auto;vertical-align:middle;image-rendering:-webkit-optimize-contrast;aspect-ratio:attr(width)/attr(height);display:inline-block;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}img:not([src],[srcset]){visibility:hidden}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{C as A,F as C,a as w,L as h,q as D,J as R,H as g,u as k,n as W}from"../@vue/reactivity-DiY1c2vO.js";import{w as b,b as x,n as L,g as F,$ as M,a5 as P}from"../@vue/runtime-core-C6bnekPw.js";function J(e){return A()?(C(e),!0):!1}const d=new WeakMap,z=(...e)=>{var t;const r=e[0],n=(t=F())==null?void 0:t.proxy;if(n==null&&!M())throw new Error("injectLocal must be called in setup");return n&&d.has(n)&&r in d.get(n)?d.get(n)[r]:P(...e)},B=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const K=e=>typeof e<"u",I=Object.prototype.toString,Q=e=>I.call(e)==="[object Object]",m=()=>{};function j(e,t){function r(...n){return new Promise((a,o)=>{Promise.resolve(e(()=>t.apply(this,n),{fn:t,thisArg:this,args:n})).then(a).catch(o)})}return r}const S=e=>e();function V(...e){let t=0,r,n=!0,a=m,o,s,i,u,c;!w(e[0])&&typeof e[0]=="object"?{delay:s,trailing:i=!0,leading:u=!0,rejectOnCancel:c=!1}=e[0]:[s,i=!0,u=!0,c=!1]=e;const f=()=>{r&&(clearTimeout(r),r=void 0,a(),a=m)};return O=>{const l=h(s),v=Date.now()-t,p=()=>o=O();return f(),l<=0?(t=Date.now(),p()):(v>l&&(u||!n)?(t=Date.now(),p()):i&&(o=new Promise((y,T)=>{a=c?T:y,r=setTimeout(()=>{t=Date.now(),n=!0,y(p()),f()},Math.max(0,l-v))})),!u&&!r&&(r=setTimeout(()=>n=!0,l)),n=!1,o)}}function E(e=S,t={}){const{initialState:r="active"}=t,n=N(r==="active");function a(){n.value=!1}function o(){n.value=!0}const s=(...i)=>{n.value&&e(...i)};return{isActive:g(n),pause:a,resume:o,eventFilter:s}}function U(e){return e.endsWith("rem")?Number.parseFloat(e)*16:Number.parseFloat(e)}function G(e){return F()}function X(e){return Array.isArray(e)?e:[e]}function N(...e){if(e.length!==1)return R(...e);const t=e[0];return typeof t=="function"?g(k(()=>({get:t,set:m}))):W(t)}function Y(e,t=200,r=!1,n=!0,a=!1){return j(V(t,r,n,a),e)}function _(e,t,r={}){const{eventFilter:n=S,...a}=r;return b(e,j(n,t),a)}function Z(e,t,r={}){const{eventFilter:n,initialState:a="active",...o}=r,{eventFilter:s,pause:i,resume:u,isActive:c}=E(n,{initialState:a});return{stop:_(e,t,{...o,eventFilter:s}),pause:i,resume:u,isActive:c}}function ee(e,t=!0,r){G()?x(e,r):t?e():L(e)}function te(e=!1,t={}){const{truthyValue:r=!0,falsyValue:n=!1}=t,a=w(e),o=D(e);function s(i){if(arguments.length)return o.value=i,o.value;{const u=h(r);return o.value=o.value===u?h(n):u,o.value}}return a?s:[o,s]}function ne(e,t,r){return b(e,t,{...r,immediate:!0})}export{N as a,ee as b,Q as c,X as d,Z as e,z as f,K as g,Y as h,B as i,U as p,J as t,te as u,ne as w};
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
import{i as C,Q as y,a as E}from"./editor-Cyf37SuL.js";import{ak as g,I as h,f as w,b as P,w as O,aJ as b}from"../@vue/runtime-core-C6bnekPw.js";import{n as d,t as $,q as F}from"../@vue/reactivity-DiY1c2vO.js";var B=Object.defineProperty,D=Object.defineProperties,j=Object.getOwnPropertyDescriptors,m=Object.getOwnPropertySymbols,H=Object.prototype.hasOwnProperty,S=Object.prototype.propertyIsEnumerable,_=(e,t,o)=>t in e?B(e,t,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[t]=o,A=(e,t)=>{for(var o in t||(t={}))H.call(t,o)&&_(e,o,t[o]);if(m)for(var o of m(t))S.call(t,o)&&_(e,o,t[o]);return e},M=(e,t)=>D(e,j(t));function u(e){let t=`请使用 '@${e}' 事件,不要放在 props 中`;return t+=`
|
||||
Please use '@${e}' event instead of props`,t}var v=(e,t)=>{for(const[o,a]of t)e[o]=a;return e};const V=w({props:{mode:{type:String,default:"default"},defaultContent:{type:Array,default:[]},defaultHtml:{type:String,default:""},defaultConfig:{type:Object,default:{}},modelValue:{type:String,default:""}},setup(e,t){const o=d(null),a=F(null),i=d(""),s=()=>{if(!o.value)return;const f=$(e.defaultContent);C({selector:o.value,mode:e.mode,content:f||[],html:e.defaultHtml||e.modelValue||"",config:M(A({},e.defaultConfig),{onCreated(r){if(a.value=r,t.emit("onCreated",r),e.defaultConfig.onCreated){const n=u("onCreated");throw new Error(n)}},onChange(r){const n=r.getHtml();if(i.value=n,t.emit("update:modelValue",n),t.emit("onChange",r),e.defaultConfig.onChange){const l=u("onChange");throw new Error(l)}},onDestroyed(r){if(t.emit("onDestroyed",r),e.defaultConfig.onDestroyed){const n=u("onDestroyed");throw new Error(n)}},onMaxLength(r){if(t.emit("onMaxLength",r),e.defaultConfig.onMaxLength){const n=u("onMaxLength");throw new Error(n)}},onFocus(r){if(t.emit("onFocus",r),e.defaultConfig.onFocus){const n=u("onFocus");throw new Error(n)}},onBlur(r){if(t.emit("onBlur",r),e.defaultConfig.onBlur){const n=u("onBlur");throw new Error(n)}},customAlert(r,n){if(t.emit("customAlert",r,n),e.defaultConfig.customAlert){const l=u("customAlert");throw new Error(l)}},customPaste:(r,n)=>{if(e.defaultConfig.customPaste){const c=u("customPaste");throw new Error(c)}let l;return t.emit("customPaste",r,n,c=>{l=c}),l}})})};function p(f){const r=a.value;r!=null&&r.setHtml(f)}return P(()=>{s()}),O(()=>e.modelValue,f=>{f!==i.value&&p(f)}),{box:o}}}),I={ref:"box",style:{height:"100%"}};function L(e,t,o,a,i,s){return g(),h("div",I,null,512)}var J=v(V,[["render",L]]);const T=w({props:{editor:{type:Object},mode:{type:String,default:"default"},defaultConfig:{type:Object,default:{}}},setup(e){const t=d(null),o=a=>{if(t.value){if(a==null)throw new Error("Not found instance of Editor when create <Toolbar/> component");y.getToolbar(a)||E({editor:a,selector:t.value||"<div></div>",mode:e.mode,config:e.defaultConfig})}};return b(()=>{const{editor:a}=e;a!=null&&o(a)}),{selector:t}}}),R={ref:"selector"};function k(e,t,o,a,i,s){return g(),h("div",R,null,512)}var N=v(T,[["render",k]]);export{J as E,N as T};
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
.cell-stack[data-v-7005d187]{display:flex;flex-direction:column;gap:2px;line-height:1.35}
|
||||
@@ -1 +0,0 @@
|
||||
import{o as C,R as D,q as d,r as I,ac as N,O as u,bj as B,D as o,v as l,bk as E,br as L,L as i,T as r,s as p,bi as R,M as w}from"./.pnpm-BGZW0UGg.js";import{a as V}from"./doctor-CF92xvl4.js";import{m as A,_ as M}from"./index-CeIwrh_6.js";const P={class:"appointment-record-panel"},$={class:"cell-stack"},j={class:"font-medium"},q={class:"text-gray-500 text-sm"},O={class:"cell-stack"},F={class:"text-gray-500 text-sm"},G=C({__name:"AppointmentRecordPanel",props:{diagnosisId:{}},setup(v,{expose:y}){const f=v,m=w(!1),g=w([]),h=w({});function x(t){return t?String(t).length>=8?String(t).slice(0,5):t:""}function k(t){return t==="morning"?"上午":t==="afternoon"?"下午":t==="all"?"全天":t||"—"}function S(t){const n=t.channel_source??t.channels??"";if(n===""||n===null||n===void 0)return"—";const a=String(n),s=h.value[a]||a,c=String(t.channel_source_detail??"").trim();return c!==""?`${s}(${c})`:s}function T(t){return[...t].sort((n,a)=>{const s=String(n.appointment_date||""),c=String(a.appointment_date||"");if(s!==c)return c.localeCompare(s);const _=String(n.appointment_time||""),e=String(a.appointment_time||"");return _!==e?e.localeCompare(_):Number(a.id||0)-Number(n.id||0)})}const z=async()=>{try{const t=await A({type:"channels"}),n=((t==null?void 0:t.channels)||[]).filter(s=>s.status!==0),a={};for(const s of n)s.value!=null&&(a[String(s.value)]=s.name||String(s.value));h.value=a}catch{h.value={}}},b=async()=>{if(f.diagnosisId){m.value=!0;try{await z();const t=await V({patient_id:f.diagnosisId,diag_scope_relax:1,page_no:1,page_size:500}),n=(t==null?void 0:t.lists)||[];g.value=T(n)}catch(t){console.error(t),g.value=[]}finally{m.value=!1}}};return D(()=>f.diagnosisId,()=>{b()},{immediate:!0}),y({refresh:b}),(t,n)=>{const a=E,s=L,c=B,_=R;return d(),I("div",P,[N((d(),u(c,{data:g.value,border:"",stripe:"","empty-text":"暂无挂号记录"},{default:o(()=>[l(a,{label:"ID",prop:"id",width:"72",align:"center"}),l(a,{label:"状态",width:"100",align:"center"},{default:o(({row:e})=>[l(s,{type:e.status===1?"success":e.status===2?"info":e.status===3?"primary":"danger",size:"small",effect:"light"},{default:o(()=>[i(r(e.status_desc||"—"),1)]),_:2},1032,["type"])]),_:1}),l(a,{label:"患者(挂号人)","min-width":"150"},{default:o(({row:e})=>[p("div",$,[p("span",j,r(e.patient_name||"—"),1),p("span",q,r(e.patient_phone||""),1)])]),_:1}),l(a,{label:"挂号医生",prop:"doctor_name",width:"110","show-overflow-tooltip":""}),l(a,{label:"挂号助理",width:"110","show-overflow-tooltip":""},{default:o(({row:e})=>[i(r(e.assistant_name||"—"),1)]),_:1}),l(a,{label:"预约时间","min-width":"130"},{default:o(({row:e})=>[p("div",O,[p("span",null,r(e.appointment_date||"—"),1),p("span",F,r(x(e.appointment_time)),1)])]),_:1}),l(a,{label:"时段",width:"80",align:"center"},{default:o(({row:e})=>[i(r(k(e.period)),1)]),_:1}),l(a,{label:"类型",width:"100","show-overflow-tooltip":""},{default:o(({row:e})=>[i(r(e.appointment_type_desc||"—"),1)]),_:1}),l(a,{label:"渠道",width:"110","show-overflow-tooltip":""},{default:o(({row:e})=>[i(r(S(e)),1)]),_:1}),l(a,{label:"确认诊单",width:"92",align:"center"},{default:o(({row:e})=>[e.diagnosis_confirmed?(d(),u(s,{key:0,type:"success",size:"small",effect:"plain"},{default:o(()=>[...n[0]||(n[0]=[i("已确认",-1)])]),_:1})):(d(),u(s,{key:1,type:"warning",size:"small",effect:"plain"},{default:o(()=>[...n[1]||(n[1]=[i("未确认",-1)])]),_:1}))]),_:1}),l(a,{label:"开方",width:"80",align:"center"},{default:o(({row:e})=>[e.has_prescription?(d(),u(s,{key:0,type:"success",size:"small",effect:"plain"},{default:o(()=>[...n[2]||(n[2]=[i("已开方",-1)])]),_:1})):(d(),u(s,{key:1,type:"info",size:"small",effect:"plain"},{default:o(()=>[...n[3]||(n[3]=[i("未开方",-1)])]),_:1}))]),_:1}),l(a,{label:"备注",prop:"remark","min-width":"100","show-overflow-tooltip":""}),l(a,{label:"创建时间",width:"165",prop:"create_time"})]),_:1},8,["data"])),[[_,m.value]])])}}}),Q=M(G,[["__scopeId","data-v-7005d187"]]);export{Q as default};
|
||||
@@ -0,0 +1 @@
|
||||
.cell-stack[data-v-4de87dfa]{display:flex;flex-direction:column;gap:2px;line-height:1.35}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{o as T,R as C,q as _,r as b,ac as D,O as h,bj as $,D as r,v as n,bk as L,L as c,T as d,br as k,s as E,a1 as A,w as P,u as B,ch as F,bi as M,M as w}from"./.pnpm-BGZW0UGg.js";import{a6 as V}from"./tcm-o2aZele6.js";import{_ as q}from"./index-CeIwrh_6.js";const K={class:"assign-log-panel"},j={key:1,class:"text-gray-400"},z=T({__name:"AssignLogPanel",props:{diagnosisId:{}},setup(v,{expose:y}){const u=v,m=w(!1),p=w([]);function N(a){const e=a.related_po_creator_name;if(e!=null&&String(e).trim()!=="")return String(e);const t=Number(a.related_po_creator_id);return Number.isFinite(t)&&t>0?`ID:${t}`:"—"}function x(a){const e=a.related_po_create_time_text;if(e!=null&&String(e).trim()!=="")return String(e);const t=Number(a.related_po_create_time);if(!Number.isFinite(t)||t<=0)return"—";const s=new Date(t*1e3);if(Number.isNaN(s.getTime()))return"—";const o=l=>String(l).padStart(2,"0");return`${s.getFullYear()}-${o(s.getMonth()+1)}-${o(s.getDate())} ${o(s.getHours())}:${o(s.getMinutes())}:${o(s.getSeconds())}`}function f(a,e){const t=e==="from"?"from_assistant_name":"to_assistant_name",s=e==="from"?"from_assistant_id":"to_assistant_id",o=a[t];if(o!=null&&String(o).trim()!==""&&String(o)!=="—")return String(o);const l=Number(a[s]);return Number.isFinite(l)&&l>0?`ID:${l}`:"—"}const g=async()=>{if(u.diagnosisId){m.value=!0;try{const a=await V({id:u.diagnosisId}),e=Array.isArray(a)?a:[];p.value=e}catch(a){console.error(a),p.value=[]}finally{m.value=!1}}};return C(()=>u.diagnosisId,()=>{g()},{immediate:!0}),y({refresh:g}),(a,e)=>{const t=L,s=k,o=P,l=A,S=$,I=M;return _(),b("div",K,[D((_(),h(S,{data:p.value,border:"",stripe:"","empty-text":"暂无指派记录"},{default:r(()=>[n(t,{label:"操作时间",width:"175",prop:"create_time_text"}),n(t,{label:"原医助","min-width":"120"},{default:r(({row:i})=>[c(d(f(i,"from")),1)]),_:1}),n(t,{label:"新医助","min-width":"120"},{default:r(({row:i})=>[c(d(f(i,"to")),1)]),_:1}),n(t,{label:"继承",width:"72",align:"center"},{default:r(({row:i})=>[Number(i.is_inherit)===1?(_(),h(s,{key:0,type:"success",size:"small"},{default:r(()=>[...e[0]||(e[0]=[c("是",-1)])]),_:1})):(_(),b("span",j,"否"))]),_:1}),n(t,{label:"快照·业务单创建人","min-width":"130","show-overflow-tooltip":""},{default:r(({row:i})=>[c(d(N(i)),1)]),_:1}),n(t,{label:"快照·业务单创建时间",width:"190"},{header:r(()=>[e[1]||(e[1]=E("span",null,"快照·业务单创建时间",-1)),n(l,{placement:"top",content:"related_po_create_time:触发本次操作的处方业务订单 create_time;与原文助一致时表示医助创建订单时间"},{default:r(()=>[n(o,{class:"assign-log-col-hint"},{default:r(()=>[n(B(F))]),_:1})]),_:1})]),default:r(({row:i})=>[c(d(x(i)),1)]),_:1}),n(t,{label:"操作人",width:"110",prop:"operator_name"}),n(t,{label:"操作账号",width:"120",prop:"operator_account","show-overflow-tooltip":""}),n(t,{label:"IP",width:"130",prop:"ip","show-overflow-tooltip":""})]),_:1},8,["data"])),[[I,m.value]])])}}}),Y=q(z,[["__scopeId","data-v-0a09e3d2"]]);export{Y as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{M as T,N as C,T as $,r as D,d as L,L as k}from"./element-plus-Bolc0EfP.js";import{Y as E}from"./@element-plus/icons-vue-B0jSCQ-G.js";import{a8 as P}from"./tcm-Dsvdp1dm.js";import{f as A,w as B,ak as p,I as b,aP as F,G as h,aN as n,a as i,O as m,J as M}from"./@vue/runtime-core-C6bnekPw.js";import{Q as c}from"./@vue/shared-mAAVTE9n.js";import{y as V,n as w}from"./@vue/reactivity-DiY1c2vO.js";import{_ as K}from"./index-d3j0BX4t.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./vue-router-QlpZ4wdW.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const Y={class:"assign-log-panel"},q={key:1,class:"text-gray-400"},z=A({__name:"AssignLogPanel",props:{diagnosisId:{}},setup(N,{expose:v}){const _=N,d=w(!1),u=w([]);function y(o){const e=o.related_po_creator_name;if(e!=null&&String(e).trim()!=="")return String(e);const t=Number(o.related_po_creator_id);return Number.isFinite(t)&&t>0?`ID:${t}`:"—"}function x(o){const e=o.related_po_create_time_text;if(e!=null&&String(e).trim()!=="")return String(e);const t=Number(o.related_po_create_time);if(!Number.isFinite(t)||t<=0)return"—";const a=new Date(t*1e3);if(Number.isNaN(a.getTime()))return"—";const r=l=>String(l).padStart(2,"0");return`${a.getFullYear()}-${r(a.getMonth()+1)}-${r(a.getDate())} ${r(a.getHours())}:${r(a.getMinutes())}:${r(a.getSeconds())}`}function f(o,e){const t=e==="from"?"from_assistant_name":"to_assistant_name",a=e==="from"?"from_assistant_id":"to_assistant_id",r=o[t];if(r!=null&&String(r).trim()!==""&&String(r)!=="—")return String(r);const l=Number(o[a]);return Number.isFinite(l)&&l>0?`ID:${l}`:"—"}const g=async()=>{if(_.diagnosisId){d.value=!0;try{const o=await P({id:_.diagnosisId}),e=Array.isArray(o)?o:[];u.value=e}catch(o){console.error(o),u.value=[]}finally{d.value=!1}}};return B(()=>_.diagnosisId,()=>{g()},{immediate:!0}),v({refresh:g}),(o,e)=>{const t=C,a=$,r=L,l=D,S=T,I=k;return p(),b("div",Y,[F((p(),h(S,{data:u.value,border:"",stripe:"","empty-text":"暂无指派记录"},{default:n(()=>[i(t,{label:"操作时间",width:"175",prop:"create_time_text"}),i(t,{label:"原医助","min-width":"120"},{default:n(({row:s})=>[m(c(f(s,"from")),1)]),_:1}),i(t,{label:"新医助","min-width":"120"},{default:n(({row:s})=>[m(c(f(s,"to")),1)]),_:1}),i(t,{label:"继承",width:"72",align:"center"},{default:n(({row:s})=>[Number(s.is_inherit)===1?(p(),h(a,{key:0,type:"success",size:"small"},{default:n(()=>[...e[0]||(e[0]=[m("是",-1)])]),_:1})):(p(),b("span",q,"否"))]),_:1}),i(t,{label:"快照·业务单创建人","min-width":"130","show-overflow-tooltip":""},{default:n(({row:s})=>[m(c(y(s)),1)]),_:1}),i(t,{label:"快照·业务单创建时间",width:"190"},{header:n(()=>[e[1]||(e[1]=M("span",null,"快照·业务单创建时间",-1)),i(l,{placement:"top",content:"related_po_create_time:触发本次操作的处方业务订单 create_time;与原文助一致时表示医助创建订单时间"},{default:n(()=>[i(r,{class:"assign-log-col-hint"},{default:n(()=>[i(V(E))]),_:1})]),_:1})]),default:n(({row:s})=>[m(c(x(s)),1)]),_:1}),i(t,{label:"操作人",width:"110",prop:"operator_name"}),i(t,{label:"操作账号",width:"120",prop:"operator_account","show-overflow-tooltip":""}),i(t,{label:"IP",width:"130",prop:"ip","show-overflow-tooltip":""})]),_:1},8,["data"])),[[I,d.value]])])}}}),Ct=K(z,[["__scopeId","data-v-f670e3e6"]]);export{Ct as default};
|
||||
@@ -1 +0,0 @@
|
||||
.assign-log-col-hint[data-v-0a09e3d2]{margin-left:4px;vertical-align:middle;color:var(--el-text-color-secondary);cursor:help}
|
||||
@@ -0,0 +1 @@
|
||||
.assign-log-col-hint[data-v-f670e3e6]{margin-left:4px;vertical-align:middle;color:var(--el-text-color-secondary);cursor:help}
|
||||
@@ -0,0 +1 @@
|
||||
.watch-state[data-v-7aff665d]{min-height:200px;display:flex;align-items:center;justify-content:center;color:var(--el-text-color-secondary);font-size:14px}.watch-error[data-v-7aff665d]{color:var(--el-color-danger)}.watch-grid[data-v-7aff665d]{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;min-height:220px}.watch-tile[data-v-7aff665d]{background:#0f0f0f;border-radius:8px;overflow:hidden;aspect-ratio:16 / 10;display:flex;flex-direction:column}.watch-tile-cap[data-v-7aff665d]{padding:6px 10px;font-size:12px;color:#e5e5e5;background:#0000008c}.watch-tile-view[data-v-7aff665d]{flex:1;min-height:0;position:relative}.watch-hint[data-v-7aff665d]{float:left;line-height:32px;font-size:12px;color:var(--el-text-color-secondary)}
|
||||
@@ -1 +0,0 @@
|
||||
.watch-state[data-v-8c719418]{min-height:200px;display:flex;align-items:center;justify-content:center;color:var(--el-text-color-secondary);font-size:14px}.watch-error[data-v-8c719418]{color:var(--el-color-danger)}.watch-grid[data-v-8c719418]{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;min-height:220px}.watch-tile[data-v-8c719418]{background:#0f0f0f;border-radius:8px;overflow:hidden;aspect-ratio:16 / 10;display:flex;flex-direction:column}.watch-tile-cap[data-v-8c719418]{padding:6px 10px;font-size:12px;color:#e5e5e5;background:#0000008c}.watch-tile-view[data-v-8c719418]{flex:1;min-height:0;position:relative}.watch-hint[data-v-8c719418]{float:left;line-height:32px;font-size:12px;color:var(--el-text-color-secondary)}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{o as B,R as D,q as E,O,D as I,r as _,T as S,P as x,ac as W,s as h,ad as P,v as U,K as $,L as j,bt as H,p as K,M as v,cW as d}from"./.pnpm-BGZW0UGg.js";import{a7 as Y}from"./tcm-o2aZele6.js";import{_ as q}from"./index-CeIwrh_6.js";const z={key:0,class:"watch-state"},F={key:1,class:"watch-state watch-error"},G=B({__name:"AssistantWatchCallDialog",props:{modelValue:{type:Boolean},diagnosisId:{}},emits:["update:modelValue","closed"],setup(R,{emit:A}){const u=R,g=A,y=K({get:()=>u.modelValue,set:t=>g("update:modelValue",t)}),r=v(null),l=v(!1),i=v(""),p=v("旁观视频通话"),n=new Map;let a=null,f=0;function w(t,e){return`${t}\0${String(e)}`}function N(t){return t.startsWith("patient_")?"患者":t.startsWith("doctor_")?"医护":t}async function T(t){if(!a||!r.value||t.streamType!==d.TYPE.STREAM_TYPE_MAIN)return;const e=w(t.userId,t.streamType);if(n.has(e))return;const o=document.createElement("div");o.className="watch-tile";const s=document.createElement("div");s.className="watch-tile-cap",s.textContent=N(t.userId);const c=document.createElement("div");c.className="watch-tile-view",o.appendChild(s),o.appendChild(c),r.value.appendChild(o),n.set(e,{wrap:o,userId:t.userId,streamType:t.streamType});try{await a.startRemoteVideo({userId:t.userId,streamType:t.streamType,view:c})}catch(b){console.warn("[AssistantWatchCall] startRemoteVideo",b)}}async function V(t){if(!a)return;const e=w(t.userId,t.streamType),o=n.get(e);if(o){try{await a.stopRemoteVideo({userId:t.userId,streamType:t.streamType})}catch{}o.wrap.remove(),n.delete(e)}}function C(){a&&(a.on(d.EVENT.REMOTE_VIDEO_AVAILABLE,T),a.on(d.EVENT.REMOTE_VIDEO_UNAVAILABLE,V))}function k(){a&&(a.off(d.EVENT.REMOTE_VIDEO_AVAILABLE,T),a.off(d.EVENT.REMOTE_VIDEO_UNAVAILABLE,V))}async function m(){if(k(),a){for(const[,t]of n){try{await a.stopRemoteVideo({userId:t.userId,streamType:t.streamType})}catch{}t.wrap.remove()}n.clear(),r.value&&(r.value.innerHTML="");try{await a.exitRoom()}catch{}try{a.destroy()}catch{}a=null}else n.clear(),r.value&&(r.value.innerHTML="")}async function L(){const t=++f;if(await m(),!u.diagnosisId){i.value="诊单无效";return}l.value=!0,i.value="",p.value="旁观视频通话";try{const e=await Y({diagnosis_id:u.diagnosisId});if(t!==f)return;e.patientName&&(p.value=`旁观视频通话 · ${e.patientName}`),a=d.create(),C();const o={sdkAppId:e.sdkAppId,userId:e.userId,userSig:e.userSig,autoReceiveAudio:!0,autoReceiveVideo:!0,...e.roomId!=null&&e.roomId>0?{roomId:e.roomId}:{strRoomId:e.strRoomId}};if(!(e.roomId!=null&&e.roomId>0)&&!e.strRoomId)throw new Error("缺少房间号");if(await a.enterRoom(o),t!==f){await m();return}l.value=!1}catch(e){l.value=!1;let o="进入房间失败";if(typeof e=="string")o=e;else if(e&&typeof e=="object"){const s=e;s.msg?o=String(s.msg):s.message&&(o=String(s.message))}i.value=o,await m()}}function M(){m(),l.value=!1,i.value="",p.value="旁观视频通话",g("closed")}return D(()=>[u.modelValue,u.diagnosisId],([t,e])=>{if(!t){f++,m();return}e>0&&L()}),(t,e)=>{const o=$,s=H;return E(),O(s,{modelValue:y.value,"onUpdate:modelValue":e[1]||(e[1]=c=>y.value=c),title:p.value,width:"760px","destroy-on-close":"","append-to-body":"","close-on-click-modal":!1,class:"assistant-watch-call-dialog",onClosed:M},{footer:I(()=>[e[3]||(e[3]=h("span",{class:"watch-hint"},"仅观看,不会开启摄像头与麦克风",-1)),U(o,{type:"primary",onClick:e[0]||(e[0]=c=>y.value=!1)},{default:I(()=>[...e[2]||(e[2]=[j("离开",-1)])]),_:1})]),default:I(()=>[l.value?(E(),_("div",z,"正在连接房间…")):i.value?(E(),_("div",F,S(i.value),1)):x("",!0),W(h("div",{ref_key:"gridRef",ref:r,class:"watch-grid"},null,512),[[P,!l.value&&!i.value]])]),_:1},8,["modelValue","title"])}}}),Z=q(G,[["__scopeId","data-v-8c719418"]]);export{Z as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.blood-record-list[data-v-3a187401]{padding:20px}
|
||||
@@ -1 +0,0 @@
|
||||
.blood-record-list[data-v-002163f9]{padding:20px}
|
||||
@@ -1 +0,0 @@
|
||||
import{o as N,cY as O,R as P,q as d,r as f,v as o,D as i,K as V,L as n,P as h,ac as D,O as w,bj as L,bk as z,T as u,s as y,bi as M,M as v}from"./.pnpm-BGZW0UGg.js";import j from"./RecordingPlaybackBlock-By4EH6-v.js";import{U as x}from"./index-TD4t951S.js";import{i as c,_ as q}from"./index-CeIwrh_6.js";import{ab as K,ac as k,ad as Y}from"./tcm-o2aZele6.js";import"./RecordingVideoPlayer-CF68w3pY.js";import"./file-tQw_m7Aj.js";const A={class:"call-record-panel"},F={key:0,class:"call-record-toolbar"},G={class:"call-record-empty"},H={class:"call-record-empty__desc"},J={key:0,class:"text-primary"},Q={key:1,class:"text-gray-400"},W=N({__name:"CallRecordPanel",props:{diagnosisId:{},readOnly:{type:Boolean,default:!1}},setup(_,{expose:R}){const r=_,p=v(!1),g=v([]),U=O("toolbarUploadRef"),m=async()=>{if(r.diagnosisId){p.value=!0;try{g.value=await K({diagnosis_id:r.diagnosisId})||[]}catch(e){console.error(e),g.value=[]}finally{p.value=!1}}};P(()=>r.diagnosisId,()=>{m()},{immediate:!0}),R({refresh:m});function C(e){return{1:"进行中",2:"已结束",3:"未接听",4:"已取消"}[e]??"—"}async function S(e){const t=b(e);if(!t){c.msgError("上传成功但未返回视频地址");return}try{const a=await Y({diagnosis_id:r.diagnosisId});await k({diagnosis_id:r.diagnosisId,call_record_id:Number((a==null?void 0:a.id)||0),file_url:t}),c.msgSuccess("视频回放上传成功"),await m()}catch(a){c.msgError((a==null?void 0:a.message)||"写入回放失败")}}async function E(e,t){const a=b(t);if(!a){c.msgError("上传成功但未返回视频地址");return}try{await k({diagnosis_id:r.diagnosisId,call_record_id:Number(e.id||0),file_url:a}),c.msgSuccess("视频回放上传成功"),await m()}catch(l){c.msgError((l==null?void 0:l.message)||"写入回放失败")}}function b(e){var t,a;return String(((t=e==null?void 0:e.data)==null?void 0:t.uri)||((a=e==null?void 0:e.data)==null?void 0:a.url)||"").trim()}return(e,t)=>{const a=V,l=z,I=L,B=M;return d(),f("div",A,[_.readOnly?h("",!0):(d(),f("div",F,[o(x,{ref_key:"toolbarUploadRef",ref:U,type:"video",direct:"",multiple:!1,limit:1,"show-progress":!0,onSuccess:S},{default:i(()=>[o(a,{type:"primary"},{default:i(()=>[...t[0]||(t[0]=[n("上传视频",-1)])]),_:1})]),_:1},512)])),D((d(),w(I,{data:g.value,border:"",stripe:""},{empty:i(()=>[y("div",G,[t[1]||(t[1]=y("div",{class:"call-record-empty__title"},"暂无通话记录",-1)),y("div",H,u(_.readOnly?"暂无录制回放数据。":"现在可以直接点击上方“上传视频”。系统会自动生成一条默认通话记录来承载回放。"),1)])]),default:i(()=>[o(l,{label:"录制回放","min-width":"320"},{default:i(({row:s})=>[o(j,{"record-id":s.id,urls:s.recording_urls_list},null,8,["record-id","urls"])]),_:1}),o(l,{label:"开始时间",width:"170",prop:"start_time_text"}),o(l,{label:"结束时间",width:"170",prop:"end_time_text"}),o(l,{label:"通话类型",width:"100"},{default:i(({row:s})=>[n(u(s.call_type===1?"语音":"视频"),1)]),_:1}),o(l,{label:"房间号",width:"180"},{default:i(({row:s})=>[s.room_id?(d(),f("span",J,u(s.room_id),1)):(d(),f("span",Q,"—"))]),_:1}),o(l,{label:"时长",width:"110",prop:"duration_text"}),o(l,{label:"状态",width:"90"},{default:i(({row:s})=>[n(u(C(s.status)),1)]),_:1}),o(l,{label:"录制",width:"100"},{default:i(({row:s})=>[n(u(s.recording_status_text||"—"),1)]),_:1}),_.readOnly?h("",!0):(d(),w(l,{key:0,label:"上传回放",width:"180"},{default:i(({row:s})=>[o(x,{type:"video",direct:"",multiple:!1,limit:1,"show-progress":!0,onSuccess:T=>E(s,T)},{default:i(()=>[o(a,{type:"primary",plain:"",size:"small"},{default:i(()=>[...t[2]||(t[2]=[n("上传视频",-1)])]),_:1})]),_:1},8,["onSuccess"])]),_:1}))]),_:1},8,["data"])),[[B,p.value]])])}}}),sa=q(W,[["__scopeId","data-v-41737096"]]);export{sa as default};
|
||||
@@ -0,0 +1 @@
|
||||
.call-record-panel .call-record-toolbar[data-v-78d5c9e4]{display:flex;align-items:center;gap:12px;margin-bottom:12px}.call-record-panel .call-record-empty[data-v-78d5c9e4]{padding:28px 12px;color:var(--el-text-color-secondary);text-align:center}.call-record-panel .call-record-empty__title[data-v-78d5c9e4]{font-size:14px;color:var(--el-text-color-primary)}.call-record-panel .call-record-empty__desc[data-v-78d5c9e4]{margin-top:8px;font-size:12px;line-height:1.6}
|
||||
@@ -1 +0,0 @@
|
||||
.call-record-panel .call-record-toolbar[data-v-41737096]{display:flex;align-items:center;gap:12px;margin-bottom:12px}.call-record-panel .call-record-empty[data-v-41737096]{padding:28px 12px;color:var(--el-text-color-secondary);text-align:center}.call-record-panel .call-record-empty__title[data-v-41737096]{font-size:14px;color:var(--el-text-color-primary)}.call-record-panel .call-record-empty__desc[data-v-41737096]{margin-top:8px;font-size:12px;line-height:1.6}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{o as T,ap as V,R as I,q as o,r as l,v as s,K as N,D as i,L as c,P as k,ac as z,bi as M,u as p,O as g,bk as O,T as _,F as P,br as j,s as F,bj as R,bB as A,M as w}from"./.pnpm-BGZW0UGg.js";import{ae as q}from"./tcm-o2aZele6.js";import{_ as H}from"./index-CeIwrh_6.js";const K={class:"case-record-list"},Y={key:0,class:"mb-3 flex justify-end"},G={key:0},J={key:1,class:"text-gray-400"},Q={class:"void-detail text-xs text-gray-500 mt-1"},U=T({__name:"CaseRecordList",props:{diagnosisId:{type:Number,default:0},readOnly:{type:Boolean,default:!1}},emits:["view","openPrescription"],setup(y,{expose:x,emit:C}){const m=y,b=C,r=w([]),d=w(!1),u=async()=>{if(m.diagnosisId){d.value=!0;try{const t=await q({diagnosis_id:m.diagnosisId});r.value=Array.isArray(t)?t:[]}catch(t){console.error("获取病历记录失败:",t),r.value=[]}finally{d.value=!1}}},S=t=>{if(!t)return"";const e=new Date(t*1e3);return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")} ${String(e.getHours()).padStart(2,"0")}:${String(e.getMinutes()).padStart(2,"0")}`},B=t=>{b("view",t)},$=()=>{b("openPrescription")};return V(()=>{u()}),I(()=>m.diagnosisId,()=>{u()}),x({refresh:u}),(t,e)=>{const h=N,n=O,v=j,E=R,D=A,L=M;return o(),l("div",K,[y.readOnly?k("",!0):(o(),l("div",Y,[s(h,{type:"primary",size:"small",onClick:$},{default:i(()=>[...e[0]||(e[0]=[c("开方",-1)])]),_:1})])),z((o(),g(E,{data:p(r),border:""},{default:i(()=>[s(n,{prop:"prescription_date",label:"就诊日期",width:"120"}),s(n,{prop:"visit_no",label:"门诊号",width:"120"}),s(n,{prop:"clinical_diagnosis",label:"临床诊断","min-width":"160","show-overflow-tooltip":""}),s(n,{label:"处方摘要","min-width":"180"},{default:i(({row:a})=>[a.herbs&&a.herbs.length?(o(),l("span",G,_(a.herbs.slice(0,3).map(f=>`${f.name}${f.dosage}克`).join("、"))+_(a.herbs.length>3?"...":""),1)):(o(),l("span",J,"—"))]),_:1}),s(n,{prop:"doctor_name",label:"医师",width:"90","show-overflow-tooltip":""}),s(n,{label:"状态",width:"140",align:"center"},{default:i(({row:a})=>[a.void_status===1?(o(),l(P,{key:0},[s(v,{type:"danger",size:"small"},{default:i(()=>[...e[1]||(e[1]=[c("已作废",-1)])]),_:1}),F("div",Q,_(a.void_by_name||"—")+" "+_(S(a.void_time)),1)],64)):(o(),g(v,{key:1,type:"success",size:"small"},{default:i(()=>[...e[2]||(e[2]=[c("正常",-1)])]),_:1}))]),_:1}),s(n,{label:"操作",width:"120",fixed:"right"},{default:i(({row:a})=>[s(h,{link:"",type:"primary",size:"small",onClick:f=>B(a)},{default:i(()=>[...e[3]||(e[3]=[c(" 查看 ",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[L,p(d)]]),!p(d)&&p(r).length===0?(o(),g(D,{key:1,description:"暂无病历记录,开方后会自动显示",class:"mt-4"})):k("",!0)])}}}),ee=H(U,[["__scopeId","data-v-da9a20f3"]]);export{ee as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{i as L,L as T,N as V,T as D,M as z,W as M}from"./element-plus-Bolc0EfP.js";import{ag as O}from"./tcm-Dsvdp1dm.js";import{f as P,b as F,w as j,ak as a,I as n,a as i,aN as s,O as m,H as w,aP as A,G as g,F as H,J as R}from"./@vue/runtime-core-C6bnekPw.js";import{y as d,n as k}from"./@vue/reactivity-DiY1c2vO.js";import{Q as c}from"./@vue/shared-mAAVTE9n.js";import{_ as G}from"./index-d3j0BX4t.js";import"./@vue/runtime-dom-DDAG46FW.js";import"./lodash-D3kF6u-c.js";import"./@element-plus/icons-vue-B0jSCQ-G.js";import"./lodash-es-C2A-Pj28.js";import"./@popperjs/core-C5az9QE8.js";import"./dayjs-DG77mNTn.js";import"./@ctrl/tinycolor-BuyEbX8F.js";import"./async-validator-CFA_igpM.js";import"./normalize-wheel-es-BQoi3Ox2.js";import"./jspdf-BsthCvR5.js";import"./@babel/runtime-BanGtE2-.js";import"./fflate-_ayOYiT1.js";import"./vue-router-QlpZ4wdW.js";import"./pinia-B8cvXbiZ.js";import"./axios-C80V62Fs.js";import"./@vueuse/core-1S4fJlii.js";import"./@vueuse/shared-Cl3lVqjz.js";import"./css-color-function-DI01JAaZ.js";import"./balanced-match-BdS7OldZ.js";import"./color-B2zxaWkI.js";import"./clone-DBZ6_OiU.js";import"./color-convert-OrFsTA_V.js";import"./color-name-Dju3oUBS.js";import"./color-string-pUQDDJII.js";import"./ms-CzQ2E3wO.js";import"./vue-clipboard3-DByT_rEQ.js";import"./clipboard-CHgxszqY.js";import"./echarts-CNLwa9wG.js";import"./tslib-BDyQ-Jie.js";import"./zrender-DRNXw7y3.js";import"./highlight.js-Bxt7hFFy.js";import"./@highlightjs/vue-plugin-C4onkBvW.js";const J={class:"case-record-list"},Q={key:0,class:"mb-3 flex justify-end"},W={key:0},Y={key:1,class:"text-gray-400"},q={class:"void-detail text-xs text-gray-500 mt-1"},K=P({__name:"CaseRecordList",props:{diagnosisId:{type:Number,default:0},readOnly:{type:Boolean,default:!1}},emits:["view","openPrescription"],setup(y,{expose:x,emit:C}){const _=y,h=C,l=k([]),p=k(!1),u=async()=>{if(_.diagnosisId){p.value=!0;try{const e=await O({diagnosis_id:_.diagnosisId});l.value=Array.isArray(e)?e:[]}catch(e){console.error("获取病历记录失败:",e),l.value=[]}finally{p.value=!1}}},S=e=>{if(!e)return"";const t=new Date(e*1e3);return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")} ${String(t.getHours()).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`},$=e=>{h("view",e)},B=()=>{h("openPrescription")};return F(()=>{u()}),j(()=>_.diagnosisId,()=>{u()}),x({refresh:u}),(e,t)=>{const b=L,r=V,v=D,E=z,N=M,I=T;return a(),n("div",J,[y.readOnly?w("",!0):(a(),n("div",Q,[i(b,{type:"primary",size:"small",onClick:B},{default:s(()=>[...t[0]||(t[0]=[m("开方",-1)])]),_:1})])),A((a(),g(E,{data:d(l),border:""},{default:s(()=>[i(r,{prop:"prescription_date",label:"就诊日期",width:"120"}),i(r,{prop:"visit_no",label:"门诊号",width:"120"}),i(r,{prop:"clinical_diagnosis",label:"临床诊断","min-width":"160","show-overflow-tooltip":""}),i(r,{label:"处方摘要","min-width":"180"},{default:s(({row:o})=>[o.herbs&&o.herbs.length?(a(),n("span",W,c(o.herbs.slice(0,3).map(f=>`${f.name}${f.dosage}克`).join("、"))+c(o.herbs.length>3?"...":""),1)):(a(),n("span",Y,"—"))]),_:1}),i(r,{prop:"doctor_name",label:"医师",width:"90","show-overflow-tooltip":""}),i(r,{label:"状态",width:"140",align:"center"},{default:s(({row:o})=>[o.void_status===1?(a(),n(H,{key:0},[i(v,{type:"danger",size:"small"},{default:s(()=>[...t[1]||(t[1]=[m("已作废",-1)])]),_:1}),R("div",q,c(o.void_by_name||"—")+" "+c(S(o.void_time)),1)],64)):(a(),g(v,{key:1,type:"success",size:"small"},{default:s(()=>[...t[2]||(t[2]=[m("正常",-1)])]),_:1}))]),_:1}),i(r,{label:"操作",width:"120",fixed:"right"},{default:s(({row:o})=>[i(b,{link:"",type:"primary",size:"small",onClick:f=>$(o)},{default:s(()=>[...t[3]||(t[3]=[m(" 查看 ",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[I,d(p)]]),!d(p)&&d(l).length===0?(a(),g(N,{key:1,description:"暂无病历记录,开方后会自动显示",class:"mt-4"})):w("",!0)])}}}),zt=G(K,[["__scopeId","data-v-043d2738"]]);export{zt as default};
|
||||
@@ -0,0 +1 @@
|
||||
.case-record-list[data-v-043d2738]{padding:20px}
|
||||
@@ -1 +0,0 @@
|
||||
.case-record-list[data-v-da9a20f3]{padding:20px}
|
||||
@@ -1 +0,0 @@
|
||||
.daily-matrix[data-v-a5368e74]{padding:16px}.daily-matrix__toolbar[data-v-a5368e74]{display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:12px}.daily-matrix__toolbar-left[data-v-a5368e74],.daily-matrix__toolbar-right[data-v-a5368e74]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.daily-matrix__table[data-v-a5368e74],.daily-matrix__table-wrap[data-v-a5368e74]{width:100%}.daily-matrix__chart[data-v-a5368e74]{margin-top:16px;padding:16px 18px;border:1px solid var(--el-border-color-lighter);border-radius:10px;background:linear-gradient(180deg,#fff,#f8fafc)}.daily-matrix__chart-canvas[data-v-a5368e74]{height:280px;width:100%}.daily-matrix__cell[data-v-a5368e74]{min-height:32px;display:inline-flex;align-items:center;justify-content:center;gap:2px;width:100%;color:var(--el-text-color-regular)}.daily-matrix__cell.is-clickable[data-v-a5368e74]{cursor:pointer}.daily-matrix__cell.is-empty[data-v-a5368e74]{color:var(--el-text-color-placeholder)}.daily-matrix__cell.is-high[data-v-a5368e74]{color:#dc2626;font-weight:700}.daily-matrix__cell.is-patient-self[data-v-a5368e74]{position:relative;background:linear-gradient(180deg,#8b5cf60a,#8b5cf61a);border-radius:4px}.daily-matrix__cell-up[data-v-a5368e74]{color:#dc2626;font-size:13px}.daily-matrix__cell-patient[data-v-a5368e74]{display:inline-block;margin-left:4px;padding:1px 6px;font-size:11px;font-weight:600;color:#6d28d9;background:#ede9fe;border:1px solid #ddd6fe;border-radius:999px;line-height:1.2;letter-spacing:.5px;white-space:nowrap}.daily-matrix__legend[data-v-a5368e74]{display:inline-flex;align-items:center;gap:6px;margin-right:12px;padding:2px 10px 2px 6px;background:#f8f6ff;border:1px dashed #ddd6fe;border-radius:999px}.daily-matrix__legend-text[data-v-a5368e74]{font-size:12px;color:#6d28d9;font-weight:500}.daily-matrix__todo[data-v-a5368e74]{margin-top:16px}.daily-matrix__section-title[data-v-a5368e74]{font-size:14px;font-weight:600;margin-bottom:12px;color:var(--el-text-color-primary)}.daily-matrix__tracking-existing[data-v-a5368e74]{width:100%;max-height:180px;overflow:auto;padding:8px 10px;border:1px solid var(--el-border-color);border-radius:6px;background:var(--el-fill-color-light)}.daily-matrix__tracking-line[data-v-a5368e74]{font-size:12.5px;line-height:1.6;color:var(--el-text-color-regular);word-break:break-word}.daily-matrix__tracking-preview[data-v-a5368e74]{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;width:100%;text-align:left}.daily-matrix__tracking-tooltip[data-v-a5368e74]{max-width:320px}.daily-matrix__tracking-tooltip-line[data-v-a5368e74]{font-size:12.5px;line-height:1.6;word-break:break-word}@media (max-width: 768px){.daily-matrix[data-v-a5368e74],.daily-matrix__chart[data-v-a5368e74]{padding:12px}.daily-matrix__chart-canvas[data-v-a5368e74]{height:240px}}
|
||||
@@ -0,0 +1 @@
|
||||
.daily-matrix[data-v-4d566204]{padding:16px}.daily-matrix__toolbar[data-v-4d566204]{display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:12px}.daily-matrix__toolbar-left[data-v-4d566204],.daily-matrix__toolbar-right[data-v-4d566204]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.daily-matrix__table[data-v-4d566204],.daily-matrix__table-wrap[data-v-4d566204]{width:100%}.daily-matrix__chart[data-v-4d566204]{margin-top:16px;padding:16px 18px;border:1px solid var(--el-border-color-lighter);border-radius:10px;background:linear-gradient(180deg,#fff,#f8fafc)}.daily-matrix__chart-canvas[data-v-4d566204]{height:280px;width:100%}.daily-matrix__cell[data-v-4d566204]{min-height:32px;display:inline-flex;align-items:center;justify-content:center;gap:2px;width:100%;color:var(--el-text-color-regular)}.daily-matrix__cell.is-clickable[data-v-4d566204]{cursor:pointer}.daily-matrix__cell.is-empty[data-v-4d566204]{color:var(--el-text-color-placeholder)}.daily-matrix__cell.is-high[data-v-4d566204]{color:#dc2626;font-weight:700}.daily-matrix__cell.is-patient-self[data-v-4d566204]{position:relative;background:linear-gradient(180deg,#8b5cf60a,#8b5cf61a);border-radius:4px}.daily-matrix__cell-up[data-v-4d566204]{color:#dc2626;font-size:13px}.daily-matrix__cell-patient[data-v-4d566204]{display:inline-block;margin-left:4px;padding:1px 6px;font-size:11px;font-weight:600;color:#6d28d9;background:#ede9fe;border:1px solid #ddd6fe;border-radius:999px;line-height:1.2;letter-spacing:.5px;white-space:nowrap}.daily-matrix__legend[data-v-4d566204]{display:inline-flex;align-items:center;gap:6px;margin-right:12px;padding:2px 10px 2px 6px;background:#f8f6ff;border:1px dashed #ddd6fe;border-radius:999px}.daily-matrix__legend-text[data-v-4d566204]{font-size:12px;color:#6d28d9;font-weight:500}.daily-matrix__todo[data-v-4d566204]{margin-top:16px}.daily-matrix__section-title[data-v-4d566204]{font-size:14px;font-weight:600;margin-bottom:12px;color:var(--el-text-color-primary)}.daily-matrix__tracking-existing[data-v-4d566204]{width:100%;max-height:180px;overflow:auto;padding:8px 10px;border:1px solid var(--el-border-color);border-radius:6px;background:var(--el-fill-color-light)}.daily-matrix__tracking-line[data-v-4d566204]{font-size:12.5px;line-height:1.6;color:var(--el-text-color-regular);word-break:break-word}.daily-matrix__tracking-preview[data-v-4d566204]{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;width:100%;text-align:left}.daily-matrix__tracking-tooltip[data-v-4d566204]{max-width:320px}.daily-matrix__tracking-tooltip-line[data-v-4d566204]{font-size:12.5px;line-height:1.6;word-break:break-word}@media(max-width:768px){.daily-matrix[data-v-4d566204],.daily-matrix__chart[data-v-4d566204]{padding:12px}.daily-matrix__chart-canvas[data-v-4d566204]{height:240px}}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.diagnosis-todo-list .toolbar[data-v-c136d72f]{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:12px}.diagnosis-todo-list .todo-table[data-v-c136d72f]{width:100%}.diagnosis-todo-list .pagination-wrap[data-v-c136d72f]{margin-top:12px;display:flex;justify-content:flex-end}.diagnosis-todo-list .text-danger[data-v-c136d72f]{color:var(--el-color-danger)}.diagnosis-todo-list .text-muted[data-v-c136d72f]{color:var(--el-text-color-placeholder)}
|
||||
@@ -1 +0,0 @@
|
||||
.diagnosis-todo-list .toolbar[data-v-e71c3261]{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:12px}.diagnosis-todo-list .todo-table[data-v-e71c3261]{width:100%}.diagnosis-todo-list .pagination-wrap[data-v-e71c3261]{margin-top:12px;display:flex;justify-content:flex-end}.diagnosis-todo-list .text-danger[data-v-e71c3261]{color:var(--el-color-danger)}.diagnosis-todo-list .text-muted[data-v-e71c3261]{color:var(--el-text-color-placeholder)}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
.diet-record-list[data-v-2ef076d1]{padding:20px}
|
||||
@@ -0,0 +1 @@
|
||||
.diet-record-list[data-v-7c66f363]{padding:20px}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user