Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7c264497e | ||
|
|
ba1c067dce | ||
|
|
5e96fda88f | ||
|
|
93e6d02ce4 | ||
|
|
752eddb0ba | ||
|
|
1005859404 | ||
|
|
ea6c73c063 | ||
|
|
3e4039efb0 | ||
|
|
c9ad4ae2ed | ||
|
|
abf8026d45 | ||
|
|
26b442b5da | ||
|
|
1cb00e9fbe | ||
|
|
9208e7eaaa | ||
|
|
a1392f9491 | ||
|
|
2ea0e1d54b | ||
|
|
d96fa6f4a1 | ||
|
|
0fb51e12d1 | ||
|
|
4f9494db0b | ||
|
|
4e87b080a0 | ||
|
|
2f3038aba1 | ||
|
|
131fb7d9c7 | ||
|
|
762a724204 | ||
|
|
8090e0aab3 | ||
|
|
9df6330d41 | ||
|
|
fd63e23eaf | ||
|
|
c1143f4629 | ||
|
|
149fdffd49 | ||
|
|
439d5a3ec9 | ||
|
|
9e24d49210 | ||
|
|
90ac43fc82 | ||
|
|
8b6b709987 | ||
|
|
fb6ca9ce85 | ||
|
|
08290b329e | ||
|
|
1d66f84bd3 | ||
|
|
a2d9c1a03f | ||
|
|
d79db88349 | ||
|
|
9f5fb650af | ||
|
|
15b4339c90 | ||
|
|
5ce331394d | ||
|
|
7ba9dabdb4 | ||
|
|
611f3dcd5c | ||
|
|
d3388b160e | ||
|
|
f83d4c06cb | ||
|
|
4941ea3e21 | ||
|
|
e35696e153 | ||
|
|
800c6af7f1 | ||
|
|
e1df584ed5 | ||
|
|
876ef0f8a0 | ||
|
|
928f75e016 | ||
|
|
37fa160c24 | ||
|
|
0eaacbb4ce | ||
|
|
1e8b5c4646 | ||
|
|
fdcfa30810 | ||
|
|
fabaa84373 | ||
|
|
da017bdf20 | ||
|
|
5d94ffab3e | ||
|
|
796ca12fb8 | ||
|
|
4caa98af3a | ||
|
|
c644f2554f | ||
|
|
bb87e4fb4c | ||
|
|
e83dac756d | ||
|
|
0c1709d589 | ||
|
|
a3892001d3 | ||
|
|
039105580c | ||
|
|
f47f431bdf | ||
|
|
7add364e20 | ||
|
|
0ebea74fde | ||
|
|
15cf6324f6 |
@@ -66,6 +66,16 @@ export function appointmentLists(params: any) {
|
|||||||
return request.get({ url: '/doctor.appointment/lists', params })
|
return request.get({ url: '/doctor.appointment/lists', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 后台编辑挂号(预约日期/时段/类型/状态/备注/医助) */
|
||||||
|
export function appointmentAdminEdit(params: any) {
|
||||||
|
return request.post({ url: '/doctor.appointment/edit', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量修改挂号渠道来源(与 edit 共用 doctor.appointment/edit 权限) */
|
||||||
|
export function appointmentBatchEditChannel(params: any) {
|
||||||
|
return request.post({ url: '/doctor.appointment/batchEditChannel', params })
|
||||||
|
}
|
||||||
|
|
||||||
// 获取挂号详情
|
// 获取挂号详情
|
||||||
export function appointmentDetail(params: any) {
|
export function appointmentDetail(params: any) {
|
||||||
return request.get({ url: '/doctor.appointment/detail', params })
|
return request.get({ url: '/doctor.appointment/detail', params })
|
||||||
|
|||||||
@@ -103,3 +103,88 @@ export function doctorDailyStatsOverview(params: {
|
|||||||
}) {
|
}) {
|
||||||
return request.get({ url: '/stats.doctorDailyStats/overview', params })
|
return request.get({ url: '/stats.doctorDailyStats/overview', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 提成结算接口计算较重,单独放宽超时(默认多为 60s) */
|
||||||
|
const COMMISSION_SETTLEMENT_TIMEOUT_MS = 120000
|
||||||
|
|
||||||
|
/** 提成结算业绩(独立于业绩看板 yejiStats) */
|
||||||
|
export function commissionSettlementOverview(params: {
|
||||||
|
settlement_month: string
|
||||||
|
/** 与 tcm.prescriptionOrder/lists 同源:create_time between */
|
||||||
|
start_time?: string
|
||||||
|
end_time?: string
|
||||||
|
/** 默认 3=履约完成,与列表 fulfillment_status 一致 */
|
||||||
|
fulfillment_status?: number
|
||||||
|
/** 传 1 时仅统计 is_system_auto=1;显式时段下默认不传(含手动) */
|
||||||
|
require_system_auto_prescription?: 0 | 1
|
||||||
|
dept_ids?: number[] | string
|
||||||
|
channel_code?: string
|
||||||
|
}) {
|
||||||
|
return request.get(
|
||||||
|
{ url: '/stats.commissionSettlement/overview', params, timeout: COMMISSION_SETTLEMENT_TIMEOUT_MS },
|
||||||
|
{ ignoreCancelToken: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function commissionSettlementDeptOptions() {
|
||||||
|
return request.get({ url: '/stats.commissionSettlement/deptOptions' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function commissionSettlementChannelOptions() {
|
||||||
|
return request.get({ url: '/stats.commissionSettlement/channelOptions' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 提成核对:订单明细分页 bucket: 空|current|deferred;appt_channel_value 可与 assistant_id/doctor_id 组合(0=未匹配挂号渠道) */
|
||||||
|
export function commissionSettlementOrderLines(params: {
|
||||||
|
settlement_month: string
|
||||||
|
start_time?: string
|
||||||
|
end_time?: string
|
||||||
|
fulfillment_status?: number
|
||||||
|
require_system_auto_prescription?: 0 | 1
|
||||||
|
dept_ids?: number[] | string
|
||||||
|
channel_code?: string
|
||||||
|
page?: number
|
||||||
|
page_size?: number
|
||||||
|
bucket?: string
|
||||||
|
assistant_id?: number
|
||||||
|
doctor_id?: number
|
||||||
|
appt_channel_value?: number
|
||||||
|
}) {
|
||||||
|
return request.get(
|
||||||
|
{ url: '/stats.commissionSettlement/orderLines', params, timeout: COMMISSION_SETTLEMENT_TIMEOUT_MS },
|
||||||
|
{ ignoreCancelToken: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function commissionSettlementConfirmStatus(params: {
|
||||||
|
settlement_month: string
|
||||||
|
dept_ids?: number[] | string
|
||||||
|
channel_code?: string
|
||||||
|
}) {
|
||||||
|
return request.get({ url: '/stats.commissionSettlement/confirmStatus', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function commissionSettlementSaveReconcile(params: Record<string, any>) {
|
||||||
|
return request.post({ url: '/stats.commissionSettlement/saveReconcile', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function commissionSettlementConfirmFinalize(params: Record<string, any>) {
|
||||||
|
return request.post(
|
||||||
|
{ url: '/stats.commissionSettlement/confirmFinalize', params, timeout: COMMISSION_SETTLEMENT_TIMEOUT_MS },
|
||||||
|
{ ignoreCancelToken: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 撤回「确定本期业绩」:清除顺延结转,状态变为可再次核对/确定 */
|
||||||
|
export function commissionSettlementConfirmRevoke(params: Record<string, any>) {
|
||||||
|
return request.post({ url: '/stats.commissionSettlement/confirmRevoke', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 医助个人业绩概览 */
|
||||||
|
export function assistantPerformanceOverview(params: {
|
||||||
|
time_type?: string
|
||||||
|
start_date?: string
|
||||||
|
end_date?: string
|
||||||
|
}) {
|
||||||
|
return request.get({ url: '/stats.assistantPerformance/overview', params })
|
||||||
|
}
|
||||||
|
|||||||
+32
-1
@@ -319,6 +319,16 @@ export function prescriptionEdit(params: any) {
|
|||||||
return request.post({ url: '/tcm.prescription/edit', params })
|
return request.post({ url: '/tcm.prescription/edit', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 仅修正处方笺患者姓名、手机号与性别(不改审核状态),写入业务订单日志表 */
|
||||||
|
export function prescriptionPatchPatient(params: {
|
||||||
|
id: number
|
||||||
|
patient_name: string
|
||||||
|
phone: string
|
||||||
|
gender: number
|
||||||
|
}) {
|
||||||
|
return request.post({ url: '/tcm.prescription/patchPatient', params })
|
||||||
|
}
|
||||||
|
|
||||||
// 删除处方
|
// 删除处方
|
||||||
export function prescriptionDelete(params: { id: number }) {
|
export function prescriptionDelete(params: { id: number }) {
|
||||||
return request.post({ url: '/tcm.prescription/delete', params })
|
return request.post({ url: '/tcm.prescription/delete', params })
|
||||||
@@ -363,6 +373,11 @@ export function prescriptionOrderLists(params: any) {
|
|||||||
return request.get({ url: '/tcm.prescriptionOrder/lists', params })
|
return request.get({ url: '/tcm.prescriptionOrder/lists', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 处方业务订单导出(export=1 预估条数,export=2 下载 Excel) */
|
||||||
|
export function prescriptionOrderExport(params: any) {
|
||||||
|
return request.get({ url: '/tcm.prescriptionOrder/export', params })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 诊单下可关联的支付单(已支付 zyt_order;已占用且未撤回的会排除;编辑时传 prescription_order_id 保留当前单已选)。
|
* 诊单下可关联的支付单(已支付 zyt_order;已占用且未撤回的会排除;编辑时传 prescription_order_id 保留当前单已选)。
|
||||||
* 服务端仅返回创建时间在 2026-04-20(含)之后的支付单;编辑时本单已关联的旧单仍会出现在列表中。
|
* 服务端仅返回创建时间在 2026-04-20(含)之后的支付单;编辑时本单已关联的旧单仍会出现在列表中。
|
||||||
@@ -447,15 +462,26 @@ export function prescriptionOrderAddPayOrder(params: {
|
|||||||
order_type: number
|
order_type: number
|
||||||
pay_amount: number
|
pay_amount: number
|
||||||
pay_remark?: string
|
pay_remark?: string
|
||||||
|
completion_request?: number
|
||||||
}) {
|
}) {
|
||||||
return request.post({ url: '/tcm.prescriptionOrder/addPayOrder', params })
|
return request.post({ url: '/tcm.prescriptionOrder/addPayOrder', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 为「已发货」订单关联已有支付单并重置支付审核为待审核 */
|
/** 为「已发货」订单关联已有支付单并重置支付审核为待审核 */
|
||||||
export function prescriptionOrderLinkPayOrder(params: { id: number; pay_order_id: number }) {
|
export function prescriptionOrderLinkPayOrder(params: {
|
||||||
|
id: number
|
||||||
|
pay_order_id?: number
|
||||||
|
pay_order_ids?: number[]
|
||||||
|
completion_request?: number
|
||||||
|
}) {
|
||||||
return request.post({ url: '/tcm.prescriptionOrder/linkPayOrder', params })
|
return request.post({ url: '/tcm.prescriptionOrder/linkPayOrder', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 已发货/已签收:仅提交完单申请(不新增/关联支付单) */
|
||||||
|
export function prescriptionOrderRequestCompletion(params: { id: number }) {
|
||||||
|
return request.post({ url: '/tcm.prescriptionOrder/requestCompletion', params })
|
||||||
|
}
|
||||||
|
|
||||||
/** 将「已发货/已签收」且支付审核通过的订单结案(3=已完成 或 7-12 业务状态) */
|
/** 将「已发货/已签收」且支付审核通过的订单结案(3=已完成 或 7-12 业务状态) */
|
||||||
export function prescriptionOrderComplete(params: { id: number; fulfillment_status: number }) {
|
export function prescriptionOrderComplete(params: { id: number; fulfillment_status: number }) {
|
||||||
return request.post({ url: '/tcm.prescriptionOrder/complete', params })
|
return request.post({ url: '/tcm.prescriptionOrder/complete', params })
|
||||||
@@ -485,6 +511,11 @@ export function prescriptionOrderUpdateAmount(params: { id: number; amount: numb
|
|||||||
return request.post({ url: '/tcm.prescriptionOrder/updateAmount', params })
|
return request.post({ url: '/tcm.prescriptionOrder/updateAmount', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 设置发货类型:gancao 甘草药房 / direct 洛阳药房 */
|
||||||
|
export function prescriptionOrderSetShipMode(params: { id: number; ship_mode: 'gancao' | 'direct' }) {
|
||||||
|
return request.post({ url: '/tcm.prescriptionOrder/setShipMode', params })
|
||||||
|
}
|
||||||
|
|
||||||
// ========== 处方库 ==========
|
// ========== 处方库 ==========
|
||||||
|
|
||||||
// 处方库列表
|
// 处方库列表
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<el-button>导出</el-button>
|
<el-button>导出</el-button>
|
||||||
</template>
|
</template>
|
||||||
<div>
|
<div>
|
||||||
|
<p v-if="props.exportHint" class="text-sm text-gray-500 mb-3 leading-relaxed">{{ props.exportHint }}</p>
|
||||||
<el-form ref="formRef" :model="formData" label-width="120px" :rules="formRules">
|
<el-form ref="formRef" :model="formData" label-width="120px" :rules="formRules">
|
||||||
<el-form-item label="数据量:">
|
<el-form-item label="数据量:">
|
||||||
预计导出{{ exportData.count }}条数据, 共{{ exportData.sum_page }}页,每页{{
|
预计导出{{ exportData.count }}条数据, 共{{ exportData.sum_page }}页,每页{{
|
||||||
@@ -79,6 +80,11 @@ const props = defineProps({
|
|||||||
fetchFun: {
|
fetchFun: {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
/** 可选:导出弹窗内提示文案(如说明与列表筛选一致) */
|
||||||
|
exportHint: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const popupRef = shallowRef<InstanceType<typeof Popup>>()
|
const popupRef = shallowRef<InstanceType<typeof Popup>>()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,582 @@
|
|||||||
|
<template>
|
||||||
|
<div class="guahao-list">
|
||||||
|
<el-card class="!border-none" shadow="never">
|
||||||
|
<el-form class="mb-[-16px]" :model="queryParams" :inline="true" @submit.prevent>
|
||||||
|
<el-form-item label="预约日期">
|
||||||
|
<daterange-picker
|
||||||
|
v-model:startTime="queryParams.start_date"
|
||||||
|
v-model:endTime="queryParams.end_date"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="患者姓名">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.patient_name"
|
||||||
|
placeholder="模糊搜索"
|
||||||
|
clearable
|
||||||
|
class="!w-[160px]"
|
||||||
|
@keyup.enter="resetPage"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="医生">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.doctor_name"
|
||||||
|
placeholder="模糊搜索"
|
||||||
|
clearable
|
||||||
|
class="!w-[140px]"
|
||||||
|
@keyup.enter="resetPage"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="医助">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.assistant_id"
|
||||||
|
placeholder="全部"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-[180px]"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="a in assistantOptions"
|
||||||
|
:key="a.id"
|
||||||
|
:label="a.name + (a.account ? ` (${a.account})` : '')"
|
||||||
|
:value="a.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="全部" clearable class="!w-[130px]">
|
||||||
|
<el-option label="已预约" :value="1" />
|
||||||
|
<el-option label="已取消" :value="2" />
|
||||||
|
<el-option label="已完成" :value="3" />
|
||||||
|
<el-option label="已过号" :value="4" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="渠道">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.channel_source"
|
||||||
|
placeholder="全部"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-[200px]"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in channelOptions"
|
||||||
|
:key="String(item.value)"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="resetPage">查询</el-button>
|
||||||
|
<el-button @click="resetFilter">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card class="!border-none mt-4" shadow="never">
|
||||||
|
<div class="flex flex-wrap items-center gap-3 mb-4">
|
||||||
|
<el-button
|
||||||
|
v-perms="['doctor.appointment/edit']"
|
||||||
|
type="primary"
|
||||||
|
:disabled="selectedRows.length === 0"
|
||||||
|
@click="openBatchChannelDialog"
|
||||||
|
>
|
||||||
|
批量修改渠道
|
||||||
|
</el-button>
|
||||||
|
<span v-if="selectedRows.length > 0" class="text-sm text-gray-500">
|
||||||
|
已选 {{ selectedRows.length }} 条
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
ref="tableRef"
|
||||||
|
v-loading="pager.loading"
|
||||||
|
row-key="id"
|
||||||
|
:data="pager.lists"
|
||||||
|
size="large"
|
||||||
|
stripe
|
||||||
|
@selection-change="onSelectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="48" align="center" reserve-selection />
|
||||||
|
<el-table-column label="ID" prop="id" width="72" align="center" />
|
||||||
|
<el-table-column label="诊单/患者" min-width="140">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="text-sm">
|
||||||
|
<div class="font-medium">{{ row.patient_name || '—' }}</div>
|
||||||
|
<div class="text-gray-500">{{ maskPhone(row.patient_phone) }}</div>
|
||||||
|
<div class="text-xs text-gray-400">诊单 #{{ row.diagnosis_id ?? row.patient_id }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="医生" prop="doctor_name" width="100" show-overflow-tooltip />
|
||||||
|
<el-table-column label="医助" prop="assistant_name" width="100" show-overflow-tooltip />
|
||||||
|
<el-table-column label="预约时间" min-width="128">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>{{ row.appointment_date }}</div>
|
||||||
|
<div class="text-gray-500">{{ formatHm(row.appointment_time) }} · {{ row.period_desc }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="类型" prop="appointment_type_desc" width="100" />
|
||||||
|
<el-table-column label="渠道" min-width="130" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="text-sm">{{ row.channel_source_desc || '—' }}</div>
|
||||||
|
<div v-if="row.channel_source_detail" class="text-xs text-gray-400 truncate">
|
||||||
|
{{ row.channel_source_detail }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" width="96" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag
|
||||||
|
:type="
|
||||||
|
row.status === 1 ? 'success' : row.status === 2 ? 'info' : row.status === 3 ? 'primary' : 'danger'
|
||||||
|
"
|
||||||
|
size="small"
|
||||||
|
effect="light"
|
||||||
|
>
|
||||||
|
{{ row.status_desc }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="备注" prop="remark" min-width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button v-perms="['doctor.appointment/edit']" type="primary" link @click="openEdit(row)">
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="flex justify-end mt-4">
|
||||||
|
<pagination v-model="pager" @change="getLists" />
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-dialog v-model="editVisible" title="编辑挂号" width="560px" destroy-on-close @closed="resetEditForm">
|
||||||
|
<el-form :model="editForm" label-width="96px">
|
||||||
|
<el-form-item label="预约日期" required>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="editForm.appointment_date"
|
||||||
|
type="date"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
placeholder="选择日期"
|
||||||
|
class="!w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="预约时间" required>
|
||||||
|
<el-time-picker
|
||||||
|
v-model="editForm.appointment_time"
|
||||||
|
format="HH:mm"
|
||||||
|
value-format="HH:mm"
|
||||||
|
placeholder="时间"
|
||||||
|
class="!w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="时段" required>
|
||||||
|
<el-radio-group v-model="editForm.period">
|
||||||
|
<el-radio-button label="morning">上午</el-radio-button>
|
||||||
|
<el-radio-button label="afternoon">下午</el-radio-button>
|
||||||
|
<el-radio-button label="all">全天</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="问诊类型" required>
|
||||||
|
<el-select v-model="editForm.appointment_type" class="!w-full">
|
||||||
|
<el-option label="视频问诊" value="video" />
|
||||||
|
<el-option label="图文问诊" value="text" />
|
||||||
|
<el-option label="电话问诊" value="phone" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="渠道来源" required>
|
||||||
|
<el-select
|
||||||
|
v-model="editForm.channel_source"
|
||||||
|
placeholder="请选择渠道来源"
|
||||||
|
filterable
|
||||||
|
class="!w-full"
|
||||||
|
@change="onChannelSourceChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in channelOptions"
|
||||||
|
:key="String(item.value)"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="needsChannelSourceDetail" label="自媒体补充" required>
|
||||||
|
<el-input
|
||||||
|
v-model="editForm.channel_source_detail"
|
||||||
|
maxlength="128"
|
||||||
|
show-word-limit
|
||||||
|
placeholder="请输入自媒体相关补充内容"
|
||||||
|
clearable
|
||||||
|
class="!w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" required>
|
||||||
|
<el-select v-model="editForm.status" class="!w-full">
|
||||||
|
<el-option label="已预约" :value="1" />
|
||||||
|
<el-option label="已取消" :value="2" />
|
||||||
|
<el-option label="已完成" :value="3" />
|
||||||
|
<el-option label="已过号" :value="4" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="挂号医助">
|
||||||
|
<el-select
|
||||||
|
v-model="editForm.assistant_id"
|
||||||
|
placeholder="不修改可留空"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-full"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="a in assistantOptions"
|
||||||
|
:key="a.id"
|
||||||
|
:label="a.name + (a.account ? ` (${a.account})` : '')"
|
||||||
|
:value="a.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注">
|
||||||
|
<el-input v-model="editForm.remark" type="textarea" :rows="3" maxlength="500" show-word-limit />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="editVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="editSaving" @click="submitEdit">保存</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="batchChannelVisible"
|
||||||
|
title="批量修改渠道"
|
||||||
|
width="520px"
|
||||||
|
destroy-on-close
|
||||||
|
@closed="resetBatchChannelForm"
|
||||||
|
>
|
||||||
|
<p class="text-sm text-gray-600 mb-4">将对已选 {{ selectedRows.length }} 条挂号写入同一渠道来源(仅改渠道,不改其它字段)。</p>
|
||||||
|
<el-form :model="batchChannelForm" label-width="96px">
|
||||||
|
<el-form-item label="渠道来源" required>
|
||||||
|
<el-select
|
||||||
|
v-model="batchChannelForm.channel_source"
|
||||||
|
placeholder="请选择渠道来源"
|
||||||
|
filterable
|
||||||
|
class="!w-full"
|
||||||
|
@change="onBatchChannelSourceChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in channelOptions"
|
||||||
|
:key="String(item.value)"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="needsBatchChannelSourceDetail" label="自媒体补充" required>
|
||||||
|
<el-input
|
||||||
|
v-model="batchChannelForm.channel_source_detail"
|
||||||
|
maxlength="128"
|
||||||
|
show-word-limit
|
||||||
|
placeholder="请输入自媒体相关补充内容"
|
||||||
|
clearable
|
||||||
|
class="!w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="batchChannelVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="batchChannelSaving" @click="submitBatchChannel">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup name="consumerPrescriptionGuahao">
|
||||||
|
import DaterangePicker from '@/components/daterange-picker/index.vue'
|
||||||
|
import { getDictData } from '@/api/app'
|
||||||
|
import { appointmentAdminEdit, appointmentBatchEditChannel, appointmentLists } from '@/api/doctor'
|
||||||
|
import { getAssistants } from '@/api/tcm'
|
||||||
|
import { usePaging } from '@/hooks/usePaging'
|
||||||
|
import feedback from '@/utils/feedback'
|
||||||
|
|
||||||
|
const queryParams = reactive({
|
||||||
|
start_date: '',
|
||||||
|
end_date: '',
|
||||||
|
patient_name: '',
|
||||||
|
doctor_name: '',
|
||||||
|
assistant_id: undefined as number | undefined,
|
||||||
|
status: '' as number | '',
|
||||||
|
channel_source: '' as string
|
||||||
|
})
|
||||||
|
|
||||||
|
const queryInit = {
|
||||||
|
start_date: '',
|
||||||
|
end_date: '',
|
||||||
|
patient_name: '',
|
||||||
|
doctor_name: '',
|
||||||
|
assistant_id: undefined as number | undefined,
|
||||||
|
status: '' as number | '',
|
||||||
|
channel_source: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pager, getLists, resetPage } = usePaging({
|
||||||
|
fetchFun: appointmentLists,
|
||||||
|
params: queryParams
|
||||||
|
})
|
||||||
|
|
||||||
|
const assistantOptions = ref<Array<{ id: number; name: string; account?: string }>>([])
|
||||||
|
|
||||||
|
const tableRef = ref<{ clearSelection?: () => void } | null>(null)
|
||||||
|
const selectedRows = ref<Record<string, unknown>[]>([])
|
||||||
|
|
||||||
|
function onSelectionChange(rows: Record<string, unknown>[]) {
|
||||||
|
selectedRows.value = rows
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 与诊单预约弹窗一致:这些字典 name 需填「自媒体补充」 */
|
||||||
|
const CHANNEL_NAMES_REQUIRING_SELF_MEDIA_DETAIL = new Set([
|
||||||
|
'自媒体4H',
|
||||||
|
'自媒体3Q',
|
||||||
|
'自媒体3H',
|
||||||
|
'自媒体2H',
|
||||||
|
'自媒体2Q'
|
||||||
|
])
|
||||||
|
|
||||||
|
function channelNameRequiresSelfMediaDetail(name: string) {
|
||||||
|
return CHANNEL_NAMES_REQUIRING_SELF_MEDIA_DETAIL.has(String(name ?? '').trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelOptions = ref<Array<{ name: string; value: string }>>([])
|
||||||
|
|
||||||
|
const selectedChannelDictName = computed(() => {
|
||||||
|
const v = editForm.channel_source
|
||||||
|
if (v === '' || v == null) return ''
|
||||||
|
const row = channelOptions.value.find((item: { value: string }) => String(item.value) === String(v))
|
||||||
|
return row ? String(row.name ?? '').trim() : ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const needsChannelSourceDetail = computed(() => channelNameRequiresSelfMediaDetail(selectedChannelDictName.value))
|
||||||
|
|
||||||
|
const batchSelectedChannelDictName = computed(() => {
|
||||||
|
const v = batchChannelForm.channel_source
|
||||||
|
if (v === '' || v == null) return ''
|
||||||
|
const row = channelOptions.value.find((item: { value: string }) => String(item.value) === String(v))
|
||||||
|
return row ? String(row.name ?? '').trim() : ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const needsBatchChannelSourceDetail = computed(() =>
|
||||||
|
channelNameRequiresSelfMediaDetail(batchSelectedChannelDictName.value)
|
||||||
|
)
|
||||||
|
|
||||||
|
function onBatchChannelSourceChange(val: string) {
|
||||||
|
const row = channelOptions.value.find((item: { value: string }) => String(item.value) === String(val))
|
||||||
|
const name = row ? String(row.name ?? '').trim() : ''
|
||||||
|
if (!channelNameRequiresSelfMediaDetail(name)) {
|
||||||
|
batchChannelForm.channel_source_detail = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchChannelVisible = ref(false)
|
||||||
|
const batchChannelSaving = ref(false)
|
||||||
|
const batchChannelForm = reactive({
|
||||||
|
channel_source: '' as string,
|
||||||
|
channel_source_detail: '' as string
|
||||||
|
})
|
||||||
|
|
||||||
|
function openBatchChannelDialog() {
|
||||||
|
if (selectedRows.value.length === 0) {
|
||||||
|
feedback.msgWarning('请先勾选挂号记录')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
batchChannelForm.channel_source = ''
|
||||||
|
batchChannelForm.channel_source_detail = ''
|
||||||
|
batchChannelVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetBatchChannelForm() {
|
||||||
|
batchChannelForm.channel_source = ''
|
||||||
|
batchChannelForm.channel_source_detail = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitBatchChannel() {
|
||||||
|
if (selectedRows.value.length === 0) {
|
||||||
|
feedback.msgWarning('请先勾选挂号记录')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!batchChannelForm.channel_source) {
|
||||||
|
feedback.msgError('请选择渠道来源')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (needsBatchChannelSourceDetail.value && !batchChannelForm.channel_source_detail.trim()) {
|
||||||
|
feedback.msgError('请填写自媒体补充说明')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
batchChannelSaving.value = true
|
||||||
|
try {
|
||||||
|
const ids = selectedRows.value.map((r) => Number(r.id))
|
||||||
|
await appointmentBatchEditChannel({
|
||||||
|
ids,
|
||||||
|
channel_source: batchChannelForm.channel_source,
|
||||||
|
channel_source_detail: batchChannelForm.channel_source_detail.trim()
|
||||||
|
})
|
||||||
|
feedback.msgSuccess('批量修改成功')
|
||||||
|
batchChannelVisible.value = false
|
||||||
|
tableRef.value?.clearSelection?.()
|
||||||
|
selectedRows.value = []
|
||||||
|
getLists()
|
||||||
|
} finally {
|
||||||
|
batchChannelSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChannelSourceChange(val: string) {
|
||||||
|
const row = channelOptions.value.find((item: { value: string }) => String(item.value) === String(val))
|
||||||
|
const name = row ? String(row.name ?? '').trim() : ''
|
||||||
|
if (!channelNameRequiresSelfMediaDetail(name)) {
|
||||||
|
editForm.channel_source_detail = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const editVisible = ref(false)
|
||||||
|
const editSaving = ref(false)
|
||||||
|
const editForm = reactive({
|
||||||
|
id: 0,
|
||||||
|
appointment_date: '',
|
||||||
|
appointment_time: '' as string,
|
||||||
|
period: 'morning' as 'morning' | 'afternoon' | 'all',
|
||||||
|
appointment_type: 'video',
|
||||||
|
status: 1,
|
||||||
|
remark: '',
|
||||||
|
assistant_id: undefined as number | undefined,
|
||||||
|
channel_source: '' as string,
|
||||||
|
channel_source_detail: '' as string
|
||||||
|
})
|
||||||
|
|
||||||
|
function maskPhone(phone?: string) {
|
||||||
|
const p = String(phone || '')
|
||||||
|
if (p.length >= 11) {
|
||||||
|
return p.slice(0, 3) + '****' + p.slice(-4)
|
||||||
|
}
|
||||||
|
return p || '—'
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatHm(t?: string) {
|
||||||
|
const s = String(t || '')
|
||||||
|
return s.length >= 5 ? s.slice(0, 5) : s || '—'
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetFilter() {
|
||||||
|
Object.assign(queryParams, queryInit)
|
||||||
|
resetPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowPeriod(row: Record<string, unknown>): 'morning' | 'afternoon' | 'all' {
|
||||||
|
const v = String(row.period ?? row.type ?? 'morning')
|
||||||
|
if (v === 'afternoon' || v === 'all') {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return 'morning'
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadAssistants() {
|
||||||
|
try {
|
||||||
|
const res: any = await getAssistants()
|
||||||
|
assistantOptions.value = res?.lists ?? res ?? []
|
||||||
|
} catch {
|
||||||
|
assistantOptions.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadChannelOptions() {
|
||||||
|
try {
|
||||||
|
const data: any = await getDictData({ type: 'channels' })
|
||||||
|
const rows = (data?.channels || []).filter((row: { status?: number }) => row.status !== 0)
|
||||||
|
rows.sort((a: { sort?: number; id?: number }, b: { sort?: number; id?: number }) => {
|
||||||
|
const ds = Number(b?.sort ?? 0) - Number(a?.sort ?? 0)
|
||||||
|
if (ds !== 0) return ds
|
||||||
|
return Number(b?.id ?? 0) - Number(a?.id ?? 0)
|
||||||
|
})
|
||||||
|
channelOptions.value = rows
|
||||||
|
} catch {
|
||||||
|
channelOptions.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEdit(row: Record<string, unknown>) {
|
||||||
|
editForm.id = Number(row.id)
|
||||||
|
editForm.appointment_date = String(row.appointment_date || '')
|
||||||
|
editForm.appointment_time = formatHm(String(row.appointment_time || ''))
|
||||||
|
editForm.period = rowPeriod(row)
|
||||||
|
editForm.appointment_type = String(row.appointment_type || 'video')
|
||||||
|
editForm.status = Number(row.status)
|
||||||
|
editForm.remark = String(row.remark || '')
|
||||||
|
const raw = row.appointment_assistant_id
|
||||||
|
editForm.assistant_id =
|
||||||
|
raw !== undefined && raw !== null && raw !== '' ? Number(raw) : undefined
|
||||||
|
editForm.channel_source = String(row.channel_source ?? row.channels ?? '')
|
||||||
|
editForm.channel_source_detail = String(row.channel_source_detail ?? '')
|
||||||
|
editVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetEditForm() {
|
||||||
|
editForm.id = 0
|
||||||
|
editForm.appointment_date = ''
|
||||||
|
editForm.appointment_time = ''
|
||||||
|
editForm.period = 'morning'
|
||||||
|
editForm.appointment_type = 'video'
|
||||||
|
editForm.status = 1
|
||||||
|
editForm.remark = ''
|
||||||
|
editForm.assistant_id = undefined
|
||||||
|
editForm.channel_source = ''
|
||||||
|
editForm.channel_source_detail = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitEdit() {
|
||||||
|
if (!editForm.appointment_date) {
|
||||||
|
feedback.msgError('请选择预约日期')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!editForm.appointment_time) {
|
||||||
|
feedback.msgError('请选择预约时间')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!editForm.channel_source) {
|
||||||
|
feedback.msgError('请选择渠道来源')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (needsChannelSourceDetail.value && !editForm.channel_source_detail.trim()) {
|
||||||
|
feedback.msgError('请填写自媒体补充说明')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
editSaving.value = true
|
||||||
|
try {
|
||||||
|
const payload: Record<string, unknown> = {
|
||||||
|
id: editForm.id,
|
||||||
|
appointment_date: editForm.appointment_date,
|
||||||
|
appointment_time: editForm.appointment_time,
|
||||||
|
period: editForm.period,
|
||||||
|
appointment_type: editForm.appointment_type,
|
||||||
|
status: editForm.status,
|
||||||
|
remark: editForm.remark,
|
||||||
|
assistant_id: editForm.assistant_id ?? '',
|
||||||
|
channel_source: editForm.channel_source,
|
||||||
|
channel_source_detail: editForm.channel_source_detail.trim()
|
||||||
|
}
|
||||||
|
await appointmentAdminEdit(payload)
|
||||||
|
feedback.msgSuccess('保存成功')
|
||||||
|
editVisible.value = false
|
||||||
|
getLists()
|
||||||
|
} finally {
|
||||||
|
editSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadAssistants()
|
||||||
|
loadChannelOptions()
|
||||||
|
})
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
getLists()
|
||||||
|
})
|
||||||
|
|
||||||
|
getLists()
|
||||||
|
</script>
|
||||||
@@ -216,6 +216,15 @@
|
|||||||
<el-button type="primary" link @click="handleView(row)" v-perms="['cf.prescription/read']">
|
<el-button type="primary" link @click="handleView(row)" v-perms="['cf.prescription/read']">
|
||||||
查看
|
查看
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="Number(row.void_status) !== 1"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
v-perms="['tcm.prescription/patchPatient']"
|
||||||
|
@click="openPatchPatientDialog(row)"
|
||||||
|
>
|
||||||
|
改姓名手机
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="!Number(row.has_prescription_order) && Number(row.void_status) !== 1"
|
v-if="!Number(row.has_prescription_order) && Number(row.void_status) !== 1"
|
||||||
type="success"
|
type="success"
|
||||||
@@ -398,20 +407,35 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rx-herbs">
|
<div class="rx-herbs">
|
||||||
<div
|
<template v-if="slipMainHerbs.length">
|
||||||
v-for="(h, i) in slipHerbsList"
|
<div class="rx-herb-section-label">主方</div>
|
||||||
:key="i"
|
<div
|
||||||
class="rx-herb-cell"
|
v-for="(h, i) in slipMainHerbs"
|
||||||
>
|
:key="'main-' + i"
|
||||||
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }}克)</span>
|
class="rx-herb-cell"
|
||||||
<span class="rx-herb-total">{{ rxHerbTotal(h.dosage) }}克</span>
|
>
|
||||||
</div>
|
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }}克)</span>
|
||||||
|
<span class="rx-herb-total">{{ rxHerbTotal(h.dosage) }}克</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="slipAuxHerbs.length">
|
||||||
|
<div class="rx-herb-section-label rx-herb-section-label--aux">辅方</div>
|
||||||
|
<div
|
||||||
|
v-for="(h, i) in slipAuxHerbs"
|
||||||
|
:key="'aux-' + i"
|
||||||
|
class="rx-herb-cell"
|
||||||
|
>
|
||||||
|
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }}克)</span>
|
||||||
|
<span class="rx-herb-total">{{ rxHerbTotal(h.dosage) }}克</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 服法 / 医嘱 / 备注 / 药房备注 / 出丸 -->
|
<!-- 服法 / 医嘱 / 备注 / 药房备注 / 出丸 -->
|
||||||
<div class="rx-text">
|
<div class="rx-text">
|
||||||
<p>服法:{{ rxUsageText }}</p>
|
<p>主方服法:{{ rxUsageText }}</p>
|
||||||
|
<p v-if="rxAuxUsageText">辅方服法:{{ rxAuxUsageText }}</p>
|
||||||
<p v-if="rxAdviceText">医嘱:{{ rxAdviceText }}</p>
|
<p v-if="rxAdviceText">医嘱:{{ rxAdviceText }}</p>
|
||||||
<p v-if="rxRemarkText">备注:{{ rxRemarkText }}</p>
|
<p v-if="rxRemarkText">备注:{{ rxRemarkText }}</p>
|
||||||
<p v-if="rxPharmacyRemarkText" class="rx-text-warn">
|
<p v-if="rxPharmacyRemarkText" class="rx-text-warn">
|
||||||
@@ -548,12 +572,20 @@
|
|||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="患者姓名" prop="patient_name">
|
<el-form-item label="患者姓名" prop="patient_name">
|
||||||
<el-input v-model="editForm.patient_name" placeholder="请输入患者姓名" />
|
<el-input
|
||||||
|
v-model="editForm.patient_name"
|
||||||
|
placeholder="请输入患者姓名"
|
||||||
|
:disabled="editMode === 'edit'"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="门诊号" prop="visit_no">
|
<el-form-item label="门诊号" prop="visit_no">
|
||||||
<el-input v-model="editForm.visit_no" placeholder="自动生成或手动输入" />
|
<el-input
|
||||||
|
v-model="editForm.visit_no"
|
||||||
|
placeholder="自动生成或手动输入"
|
||||||
|
:disabled="editMode === 'edit'"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -632,53 +664,87 @@
|
|||||||
<el-form-item label="药材配方" prop="herbs" required>
|
<el-form-item label="药材配方" prop="herbs" required>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<el-button type="primary" size="small" @click="addHerb">
|
<el-button
|
||||||
添加药材
|
type="success"
|
||||||
</el-button>
|
size="small"
|
||||||
<el-button type="success" size="small" @click="openLibraryDialog">
|
v-perms="['cf.prescription/add', 'cf.prescription/edit', 'tcm.prescriptionLibrary/lists']"
|
||||||
|
@click="openLibraryDialog"
|
||||||
|
>
|
||||||
从处方库导入
|
从处方库导入
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
|
||||||
<div class="mt-2">
|
|
||||||
<el-button type="warning" size="small" plain :icon="EditPen" @click="openPasteRecipeDialog">
|
<el-button type="warning" size="small" plain :icon="EditPen" @click="openPasteRecipeDialog">
|
||||||
导入药方
|
导入药方
|
||||||
</el-button>
|
</el-button>
|
||||||
<span class="text-xs text-gray-500 ml-2 align-middle">
|
<span class="text-xs text-gray-500">
|
||||||
粘贴文本解析药名与剂量;须与药品库名称完全一致才录入
|
粘贴文本解析药名与剂量;须与药品库名称完全一致才录入(导入至主方)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="editForm.herbs" class="mt-2" border>
|
|
||||||
<el-table-column label="序号" type="index" width="60" />
|
|
||||||
<el-table-column label="药材名称" min-width="220">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<MedicineNameSelect v-model="row.name" class="w-full" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="剂量(克)" min-width="120">
|
|
||||||
<template #default="{ row, $index }">
|
|
||||||
<el-input-number
|
|
||||||
v-model="row.dosage"
|
|
||||||
:min="0"
|
|
||||||
:precision="1"
|
|
||||||
:step="0.5"
|
|
||||||
placeholder="剂量"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="80">
|
|
||||||
<template #default="{ $index }">
|
|
||||||
<el-button
|
|
||||||
type="danger"
|
|
||||||
link
|
|
||||||
@click="removeHerb($index)"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
</el-table>
|
<div class="herb-formula-block mt-3">
|
||||||
|
<div class="herb-formula-block__head">
|
||||||
|
<el-tag type="primary" size="small">主方</el-tag>
|
||||||
|
<el-button type="primary" size="small" @click="addHerb('主方')">添加主方药材</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="mainHerbRows" class="mt-2" border empty-text="暂无主方药材">
|
||||||
|
<el-table-column label="序号" type="index" width="60" />
|
||||||
|
<el-table-column label="药材名称" min-width="220">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<MedicineNameSelect v-model="editForm.herbs[row.index].name" class="w-full" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="剂量(克)" min-width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="editForm.herbs[row.index].dosage"
|
||||||
|
:min="0"
|
||||||
|
:precision="1"
|
||||||
|
:step="0.5"
|
||||||
|
placeholder="剂量"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="danger" link @click="removeHerb(row.index)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="herb-formula-block mt-4">
|
||||||
|
<div class="herb-formula-block__head">
|
||||||
|
<el-tag type="warning" size="small">辅方</el-tag>
|
||||||
|
<el-button type="primary" size="small" plain @click="addHerb('辅方')">
|
||||||
|
添加辅方药材
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="auxHerbRows" class="mt-2" border empty-text="暂无辅方药材">
|
||||||
|
<el-table-column label="序号" type="index" width="60" />
|
||||||
|
<el-table-column label="药材名称" min-width="220">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<MedicineNameSelect v-model="editForm.herbs[row.index].name" class="w-full" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="剂量(克)" min-width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="editForm.herbs[row.index].dosage"
|
||||||
|
:min="0"
|
||||||
|
:precision="1"
|
||||||
|
:step="0.5"
|
||||||
|
placeholder="剂量"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="danger" link @click="removeHerb(row.index)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
<el-alert
|
<el-alert
|
||||||
v-if="editMode === 'edit'"
|
v-if="editMode === 'edit'"
|
||||||
type="warning"
|
type="warning"
|
||||||
@@ -761,8 +827,10 @@
|
|||||||
<el-option label="汤剂" value="汤剂" />
|
<el-option label="汤剂" value="汤剂" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item> </el-col>
|
</el-form-item> </el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<!-- 用量字段 -->
|
<div class="usage-formula-title usage-formula-title--main">主方用法</div>
|
||||||
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-form-item label="用量" prop="dosage_amount">
|
<el-form-item label="用量" prop="dosage_amount">
|
||||||
<!-- 浓缩水丸:1-10g 下拉选择 -->
|
<!-- 浓缩水丸:1-10g 下拉选择 -->
|
||||||
@@ -865,6 +933,97 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<template v-if="auxHerbRows.length > 0">
|
||||||
|
<div class="usage-formula-title usage-formula-title--aux">辅方用法</div>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="用量">
|
||||||
|
<el-select
|
||||||
|
v-if="editForm.prescription_type === '浓缩水丸'"
|
||||||
|
v-model="editForm.aux_usage.dosage_amount"
|
||||||
|
placeholder="请选择用量"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<el-option v-for="n in 10" :key="n" :label="n + 'g'" :value="n" />
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
|
v-else-if="editForm.prescription_type === '饮片'"
|
||||||
|
v-model="editForm.aux_usage.dosage_amount"
|
||||||
|
placeholder="请选择用量"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<el-option label="50ml" :value="50" />
|
||||||
|
<el-option label="100ml" :value="100" />
|
||||||
|
<el-option label="120ml" :value="120" />
|
||||||
|
<el-option label="150ml" :value="150" />
|
||||||
|
<el-option label="180ml" :value="180" />
|
||||||
|
<el-option label="200ml" :value="200" />
|
||||||
|
<el-option label="250ml" :value="250" />
|
||||||
|
</el-select>
|
||||||
|
<el-input-number
|
||||||
|
v-else
|
||||||
|
v-model="editForm.aux_usage.dosage_amount"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="editForm.prescription_type === '浓缩水丸'" :span="8">
|
||||||
|
<el-form-item label=" " label-width="12px">
|
||||||
|
<el-select
|
||||||
|
v-model="editForm.aux_usage.dosage_bag_count"
|
||||||
|
placeholder="请选择袋数"
|
||||||
|
class="w-[120px]"
|
||||||
|
>
|
||||||
|
<el-option label="1袋" :value="1" />
|
||||||
|
<el-option label="2袋" :value="2" />
|
||||||
|
<el-option label="3袋" :value="3" />
|
||||||
|
<el-option label="4袋" :value="4" />
|
||||||
|
<el-option label="5袋" :value="5" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="editForm.prescription_type === '饮片'" :span="8">
|
||||||
|
<el-form-item label="是否代煎">
|
||||||
|
<el-radio-group v-model="editForm.aux_usage.need_decoction">
|
||||||
|
<el-radio :label="true">代煎</el-radio>
|
||||||
|
<el-radio :label="false">不代煎</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="editForm.prescription_type === '饮片'" :span="8">
|
||||||
|
<el-form-item label="每贴出包数">
|
||||||
|
<el-select v-model="editForm.aux_usage.bags_per_dose" placeholder="请选择" class="w-full">
|
||||||
|
<el-option v-for="n in 9" :key="n" :label="n + '包'" :value="n" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="每天几次">
|
||||||
|
<el-input-number
|
||||||
|
v-model="editForm.aux_usage.times_per_day"
|
||||||
|
:min="1"
|
||||||
|
:max="6"
|
||||||
|
class="!w-full"
|
||||||
|
placeholder="每天服用次数"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="服用天数">
|
||||||
|
<el-input-number
|
||||||
|
v-model="editForm.aux_usage.usage_days"
|
||||||
|
:min="1"
|
||||||
|
:max="365"
|
||||||
|
placeholder="服用天数"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
<el-form-item label="用法" prop="usage_instruction">
|
<el-form-item label="用法" prop="usage_instruction">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="editForm.usage_instruction"
|
v-model="editForm.usage_instruction"
|
||||||
@@ -972,6 +1131,48 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- 修正处方笺姓名 / 手机号(zyt_tcm_prescription,写入订单日志) -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="patchPatientVisible"
|
||||||
|
title="修正姓名、性别与手机号"
|
||||||
|
width="440px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
@closed="resetPatchPatientForm"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="patchPatientFormRef"
|
||||||
|
:model="patchPatientForm"
|
||||||
|
:rules="patchPatientRules"
|
||||||
|
label-width="88px"
|
||||||
|
>
|
||||||
|
<el-form-item label="处方编号">
|
||||||
|
<span class="text-gray-700">#{{ patchPatientForm.id || '—' }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="患者姓名" prop="patient_name">
|
||||||
|
<el-input v-model="patchPatientForm.patient_name" maxlength="50" show-word-limit placeholder="处方笺展示姓名" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="性别" prop="gender">
|
||||||
|
<el-radio-group v-model="patchPatientForm.gender">
|
||||||
|
<el-radio :label="1">男</el-radio>
|
||||||
|
<el-radio :label="0">女</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="手机号" prop="phone">
|
||||||
|
<el-input v-model="patchPatientForm.phone" maxlength="20" placeholder="处方笺展示手机号" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<p class="text-xs text-gray-500 -mt-2 mb-2">
|
||||||
|
仅更新处方表 patient_name、gender、phone,不改变审核状态;记录写入业务订单操作日志(有关联订单时可在此订单日志中查看)。
|
||||||
|
</p>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="patchPatientVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="patchPatientSubmitLoading" @click="submitPatchPatient">
|
||||||
|
保存
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 从消费者处方创建业务订单(zyt_tcm_prescription_order,与支付单 zyt_order 分离) -->
|
<!-- 从消费者处方创建业务订单(zyt_tcm_prescription_order,与支付单 zyt_order 分离) -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="createOrderVisible"
|
v-model="createOrderVisible"
|
||||||
@@ -1306,16 +1507,17 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 从处方库导入(与诊间 tcm-prescription 一致,按当前处方开方医师 creator_id 筛选) -->
|
<!-- 从处方库导入:当前开方医师的全部模板 + 所有人可见的公共模板 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="showLibraryDialog"
|
v-model="showLibraryDialog"
|
||||||
title="从处方库导入"
|
title="从处方库导入"
|
||||||
width="800px"
|
width="800px"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
>
|
>
|
||||||
<div class="mb-4">
|
<div class="mb-4 flex gap-3">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="librarySearchName"
|
v-model="librarySearchName"
|
||||||
|
class="flex-1"
|
||||||
placeholder="请输入处方名称搜索"
|
placeholder="请输入处方名称搜索"
|
||||||
clearable
|
clearable
|
||||||
@keyup.enter="searchLibrary"
|
@keyup.enter="searchLibrary"
|
||||||
@@ -1325,6 +1527,24 @@
|
|||||||
<el-button :icon="Search" @click="searchLibrary" />
|
<el-button :icon="Search" @click="searchLibrary" />
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
<el-select
|
||||||
|
v-model="librarySearchFormulaType"
|
||||||
|
placeholder="处方类型"
|
||||||
|
clearable
|
||||||
|
class="w-[140px]"
|
||||||
|
@change="searchLibrary"
|
||||||
|
>
|
||||||
|
<el-option label="全部" value="" />
|
||||||
|
<el-option label="主方" value="主方" />
|
||||||
|
<el-option label="辅方" value="辅方" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 flex flex-wrap items-center gap-x-4 gap-y-2">
|
||||||
|
<span class="text-sm text-gray-600 shrink-0">导入方式</span>
|
||||||
|
<el-radio-group v-model="libraryImportMode">
|
||||||
|
<el-radio label="replace">覆盖现有药材</el-radio>
|
||||||
|
<el-radio label="append">追加到末尾</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
@@ -1335,6 +1555,13 @@
|
|||||||
@row-click="handleSelectLibraryRow"
|
@row-click="handleSelectLibraryRow"
|
||||||
>
|
>
|
||||||
<el-table-column label="处方名称" prop="prescription_name" min-width="150" show-overflow-tooltip />
|
<el-table-column label="处方名称" prop="prescription_name" min-width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column label="处方类型" width="90">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.formula_type === '辅方' ? 'warning' : 'primary'" size="small">
|
||||||
|
{{ row.formula_type || '主方' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="药材数量" width="100">
|
<el-table-column label="药材数量" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ row.herbs?.length || 0 }}味
|
{{ row.herbs?.length || 0 }}味
|
||||||
@@ -1348,6 +1575,13 @@
|
|||||||
<span v-else class="text-gray-400">暂无药材</span>
|
<span v-else class="text-gray-400">暂无药材</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="是否公开" width="110">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.is_public ? 'success' : 'info'" size="small">
|
||||||
|
{{ row.is_public ? '所有人可见' : '仅自己可见' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="创建人" prop="creator_name" width="100" />
|
<el-table-column label="创建人" prop="creator_name" width="100" />
|
||||||
<el-table-column label="操作" width="100" fixed="right">
|
<el-table-column label="操作" width="100" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@@ -1436,6 +1670,7 @@ import {
|
|||||||
prescriptionLists,
|
prescriptionLists,
|
||||||
prescriptionAdd,
|
prescriptionAdd,
|
||||||
prescriptionEdit,
|
prescriptionEdit,
|
||||||
|
prescriptionPatchPatient,
|
||||||
prescriptionDelete,
|
prescriptionDelete,
|
||||||
prescriptionAudit,
|
prescriptionAudit,
|
||||||
prescriptionDetail,
|
prescriptionDetail,
|
||||||
@@ -1462,6 +1697,119 @@ import jsPDF from 'jspdf'
|
|||||||
|
|
||||||
const TcmDiagnosisEditView = defineAsyncComponent(() => import('@/views/tcm/diagnosis/edit.vue'))
|
const TcmDiagnosisEditView = defineAsyncComponent(() => import('@/views/tcm/diagnosis/edit.vue'))
|
||||||
|
|
||||||
|
type FormulaType = '主方' | '辅方'
|
||||||
|
type HerbRow = { name: string; dosage: number; formula_type: FormulaType }
|
||||||
|
|
||||||
|
type AuxUsageForm = {
|
||||||
|
dosage_amount?: number
|
||||||
|
dosage_bag_count: number
|
||||||
|
need_decoction: boolean
|
||||||
|
bags_per_dose: number
|
||||||
|
times_per_day: number
|
||||||
|
usage_days: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultAuxUsage(prescriptionType = '浓缩水丸'): AuxUsageForm {
|
||||||
|
if (prescriptionType === '饮片') {
|
||||||
|
return {
|
||||||
|
dosage_amount: 50,
|
||||||
|
dosage_bag_count: 1,
|
||||||
|
need_decoction: false,
|
||||||
|
bags_per_dose: 1,
|
||||||
|
times_per_day: 3,
|
||||||
|
usage_days: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prescriptionType === '浓缩水丸') {
|
||||||
|
return {
|
||||||
|
dosage_amount: 5,
|
||||||
|
dosage_bag_count: 1,
|
||||||
|
need_decoction: false,
|
||||||
|
bags_per_dose: 1,
|
||||||
|
times_per_day: 3,
|
||||||
|
usage_days: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
dosage_amount: 1,
|
||||||
|
dosage_bag_count: 1,
|
||||||
|
need_decoction: false,
|
||||||
|
bags_per_dose: 1,
|
||||||
|
times_per_day: 3,
|
||||||
|
usage_days: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAuxUsageForm(raw: unknown, prescriptionType: string): AuxUsageForm {
|
||||||
|
const base = defaultAuxUsage(prescriptionType)
|
||||||
|
if (!raw || typeof raw !== 'object') return { ...base }
|
||||||
|
const o = raw as Record<string, unknown>
|
||||||
|
return {
|
||||||
|
dosage_amount:
|
||||||
|
o.dosage_amount !== null && o.dosage_amount !== undefined && o.dosage_amount !== ''
|
||||||
|
? Number(o.dosage_amount)
|
||||||
|
: base.dosage_amount,
|
||||||
|
dosage_bag_count: o.dosage_bag_count != null ? Number(o.dosage_bag_count) || 1 : base.dosage_bag_count,
|
||||||
|
need_decoction: o.need_decoction === 1 || o.need_decoction === true,
|
||||||
|
bags_per_dose: o.bags_per_dose != null ? Number(o.bags_per_dose) || 1 : base.bags_per_dose,
|
||||||
|
times_per_day: o.times_per_day != null ? Number(o.times_per_day) || 3 : base.times_per_day,
|
||||||
|
usage_days: o.usage_days != null ? Number(o.usage_days) || 7 : base.usage_days
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUsageSegmentText(
|
||||||
|
usage: {
|
||||||
|
prescription_type?: string
|
||||||
|
dosage_amount?: number | null
|
||||||
|
dosage_unit?: string
|
||||||
|
dosage_bag_count?: number
|
||||||
|
times_per_day?: number
|
||||||
|
usage_way?: string
|
||||||
|
usage_time?: string
|
||||||
|
},
|
||||||
|
fallbackWay?: string,
|
||||||
|
fallbackTime?: string
|
||||||
|
): string {
|
||||||
|
const pt = usage.prescription_type || '浓缩水丸'
|
||||||
|
const times = Number(usage.times_per_day) > 0 ? Number(usage.times_per_day) : 3
|
||||||
|
const amount = usage.dosage_amount != null ? Number(usage.dosage_amount) : 10
|
||||||
|
const unit = usage.dosage_unit || (pt === '饮片' ? 'ml' : 'g')
|
||||||
|
const usageWay = usage.usage_way || fallbackWay || '温水送服'
|
||||||
|
const usageTime = usage.usage_time || fallbackTime || ''
|
||||||
|
const seg: string[] = []
|
||||||
|
seg.push(`每天${times}次`)
|
||||||
|
if (pt === '浓缩水丸') {
|
||||||
|
const bags = Number(usage.dosage_bag_count) > 0 ? Number(usage.dosage_bag_count) : 1
|
||||||
|
seg.push(`一次${bags}袋`)
|
||||||
|
seg.push(`每袋${amount}${unit}`)
|
||||||
|
} else {
|
||||||
|
seg.push(`一次${amount}${unit}`)
|
||||||
|
}
|
||||||
|
seg.push(usageWay)
|
||||||
|
if (usageTime) seg.push(usageTime)
|
||||||
|
return seg.join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFormulaType(v: unknown): FormulaType {
|
||||||
|
return v === '辅方' ? '辅方' : '主方'
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHerbRow(raw: any): HerbRow {
|
||||||
|
return {
|
||||||
|
name: String(raw?.name ?? '').trim(),
|
||||||
|
dosage: Number(raw?.dosage) || 0,
|
||||||
|
formula_type: normalizeFormulaType(raw?.formula_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSlipHerbs(raw: unknown): HerbRow[] {
|
||||||
|
if (!raw) return []
|
||||||
|
if (Array.isArray(raw)) {
|
||||||
|
return raw.map((x: any) => normalizeHerbRow(x))
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
/** 与 server/config/project.php prescription_audit_roles 保持一致 */
|
/** 与 server/config/project.php prescription_audit_roles 保持一致 */
|
||||||
const PRESCRIPTION_AUDIT_ROLE_IDS = [0, 3]
|
const PRESCRIPTION_AUDIT_ROLE_IDS = [0, 3]
|
||||||
|
|
||||||
@@ -1523,6 +1871,66 @@ const auditTargetId = ref(0)
|
|||||||
const auditRemark = ref('')
|
const auditRemark = ref('')
|
||||||
const auditLoading = ref(false)
|
const auditLoading = ref(false)
|
||||||
|
|
||||||
|
/** 修正处方笺显示用姓名 / 手机号 */
|
||||||
|
const patchPatientVisible = ref(false)
|
||||||
|
const patchPatientSubmitLoading = ref(false)
|
||||||
|
const patchPatientFormRef = ref<FormInstance>()
|
||||||
|
const patchPatientForm = reactive({
|
||||||
|
id: 0,
|
||||||
|
patient_name: '',
|
||||||
|
gender: 1 as 0 | 1,
|
||||||
|
phone: ''
|
||||||
|
})
|
||||||
|
const patchPatientRules: FormRules = {
|
||||||
|
patient_name: [{ required: true, message: '请输入患者姓名', trigger: 'blur' }],
|
||||||
|
gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
|
||||||
|
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
function openPatchPatientDialog(row: Record<string, unknown>) {
|
||||||
|
patchPatientForm.id = Number(row.id) || 0
|
||||||
|
patchPatientForm.patient_name = String(row.patient_name ?? '').trim()
|
||||||
|
const g = Number(row.gender)
|
||||||
|
patchPatientForm.gender = g === 0 || g === 1 ? (g as 0 | 1) : 1
|
||||||
|
patchPatientForm.phone = String(row.phone ?? '').trim()
|
||||||
|
patchPatientVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetPatchPatientForm() {
|
||||||
|
patchPatientForm.id = 0
|
||||||
|
patchPatientForm.patient_name = ''
|
||||||
|
patchPatientForm.gender = 1
|
||||||
|
patchPatientForm.phone = ''
|
||||||
|
patchPatientFormRef.value?.clearValidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitPatchPatient() {
|
||||||
|
const form = patchPatientFormRef.value
|
||||||
|
if (!form) return
|
||||||
|
try {
|
||||||
|
await form.validate()
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
patchPatientSubmitLoading.value = true
|
||||||
|
try {
|
||||||
|
await prescriptionPatchPatient({
|
||||||
|
id: patchPatientForm.id,
|
||||||
|
patient_name: patchPatientForm.patient_name.trim(),
|
||||||
|
phone: patchPatientForm.phone.trim(),
|
||||||
|
gender: patchPatientForm.gender
|
||||||
|
})
|
||||||
|
feedback.msgSuccess('已保存')
|
||||||
|
patchPatientVisible.value = false
|
||||||
|
getLists()
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const msg = e && typeof e === 'object' && 'msg' in e ? String((e as { msg?: string }).msg) : ''
|
||||||
|
feedback.msgError(msg || '保存失败')
|
||||||
|
} finally {
|
||||||
|
patchPatientSubmitLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 从消费者处方创建订单 */
|
/** 从消费者处方创建订单 */
|
||||||
const createOrderVisible = ref(false)
|
const createOrderVisible = ref(false)
|
||||||
/** 创建订单分步向导:0 患者与收货 / 1 服务与支付单 / 2 金额与确认 */
|
/** 创建订单分步向导:0 患者与收货 / 1 服务与支付单 / 2 金额与确认 */
|
||||||
@@ -1803,7 +2211,13 @@ const createOrderHerbSummary = computed(() => {
|
|||||||
const row = createOrderPrescription.value
|
const row = createOrderPrescription.value
|
||||||
const herbs = normalizeSlipHerbs(row?.herbs)
|
const herbs = normalizeSlipHerbs(row?.herbs)
|
||||||
if (!herbs.length) return '暂无药材明细'
|
if (!herbs.length) return '暂无药材明细'
|
||||||
const s = herbs.map((h) => `${h.name} ${h.dosage}g`).join('、')
|
const main = herbs.filter((h) => normalizeFormulaType(h.formula_type) === '主方')
|
||||||
|
const aux = herbs.filter((h) => normalizeFormulaType(h.formula_type) === '辅方')
|
||||||
|
const fmt = (list: HerbRow[]) => list.map((h) => `${h.name} ${h.dosage}g`).join('、')
|
||||||
|
const parts: string[] = []
|
||||||
|
if (main.length) parts.push(`主方:${fmt(main)}`)
|
||||||
|
if (aux.length) parts.push(`辅方:${fmt(aux)}`)
|
||||||
|
const s = parts.join(';')
|
||||||
return s.length > 120 ? `${s.slice(0, 120)}…` : s
|
return s.length > 120 ? `${s.slice(0, 120)}…` : s
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2043,9 +2457,27 @@ const editReviveHint = computed(() => {
|
|||||||
|
|
||||||
const slipHerbsList = computed(() => {
|
const slipHerbsList = computed(() => {
|
||||||
const h = slipView.value?.herbs
|
const h = slipView.value?.herbs
|
||||||
return Array.isArray(h) ? h : []
|
return Array.isArray(h) ? (h as HerbRow[]) : []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const slipMainHerbs = computed(() =>
|
||||||
|
slipHerbsList.value.filter((h) => normalizeFormulaType(h.formula_type) === '主方')
|
||||||
|
)
|
||||||
|
const slipAuxHerbs = computed(() =>
|
||||||
|
slipHerbsList.value.filter((h) => normalizeFormulaType(h.formula_type) === '辅方')
|
||||||
|
)
|
||||||
|
|
||||||
|
const mainHerbRows = computed(() =>
|
||||||
|
editForm.herbs
|
||||||
|
.map((herb, index) => ({ herb, index }))
|
||||||
|
.filter(({ herb }) => normalizeFormulaType(herb.formula_type) === '主方')
|
||||||
|
)
|
||||||
|
const auxHerbRows = computed(() =>
|
||||||
|
editForm.herbs
|
||||||
|
.map((herb, index) => ({ herb, index }))
|
||||||
|
.filter(({ herb }) => normalizeFormulaType(herb.formula_type) === '辅方')
|
||||||
|
)
|
||||||
|
|
||||||
const slipDietaryText = computed(() => {
|
const slipDietaryText = computed(() => {
|
||||||
const d = slipView.value?.dietary_taboo
|
const d = slipView.value?.dietary_taboo
|
||||||
if (Array.isArray(d)) return d.filter(Boolean).join('、')
|
if (Array.isArray(d)) return d.filter(Boolean).join('、')
|
||||||
@@ -2131,23 +2563,26 @@ const rxUsageText = computed(() => {
|
|||||||
const v = slipView.value as any
|
const v = slipView.value as any
|
||||||
if (!v) return '—'
|
if (!v) return '—'
|
||||||
if (v.usage_text) return v.usage_text
|
if (v.usage_text) return v.usage_text
|
||||||
const times = Number(v.times_per_day) > 0 ? Number(v.times_per_day) : 3
|
return buildUsageSegmentText(v)
|
||||||
const amount = v.dosage_amount != null ? Number(v.dosage_amount) : 10
|
})
|
||||||
const unit = v.dosage_unit || 'g'
|
|
||||||
const usageWay = v.usage_way || '温水送服'
|
const rxAuxUsageText = computed(() => {
|
||||||
const usageTime = v.usage_time || ''
|
const v = slipView.value as any
|
||||||
const seg: string[] = []
|
if (!v || !slipAuxHerbs.value.length) return ''
|
||||||
seg.push(`每天${times}次`)
|
const aux = normalizeAuxUsageForm(v.aux_usage, v.prescription_type || '浓缩水丸')
|
||||||
if ((v.prescription_type || '浓缩水丸') === '浓缩水丸') {
|
return buildUsageSegmentText(
|
||||||
const bags = Number(v.dosage_bag_count) > 0 ? Number(v.dosage_bag_count) : 1
|
{
|
||||||
seg.push(`一次${bags}袋`)
|
prescription_type: v.prescription_type,
|
||||||
seg.push(`每袋${amount}${unit}`)
|
dosage_amount: aux.dosage_amount,
|
||||||
} else {
|
dosage_unit: v.dosage_unit,
|
||||||
seg.push(`一次${amount}${unit}`)
|
dosage_bag_count: aux.dosage_bag_count,
|
||||||
}
|
times_per_day: aux.times_per_day,
|
||||||
seg.push(usageWay)
|
usage_way: v.usage_way,
|
||||||
if (usageTime) seg.push(usageTime)
|
usage_time: v.usage_time
|
||||||
return seg.join(', ')
|
},
|
||||||
|
v.usage_way,
|
||||||
|
v.usage_time
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const rxAdviceText = computed(() => {
|
const rxAdviceText = computed(() => {
|
||||||
@@ -2342,7 +2777,7 @@ const editForm = reactive({
|
|||||||
pulse: '',
|
pulse: '',
|
||||||
pulse_condition: '',
|
pulse_condition: '',
|
||||||
clinical_diagnosis: '',
|
clinical_diagnosis: '',
|
||||||
herbs: [] as Array<{ name: string; dosage: number }>,
|
herbs: [] as HerbRow[],
|
||||||
dose_count: 7,
|
dose_count: 7,
|
||||||
dose_unit: '剂',
|
dose_unit: '剂',
|
||||||
usage_days: 7,
|
usage_days: 7,
|
||||||
@@ -2368,7 +2803,8 @@ const editForm = reactive({
|
|||||||
/** 列表带入:业务订单「处方审核」驳回(消费者处方本身可能仍为已通过) */
|
/** 列表带入:业务订单「处方审核」驳回(消费者处方本身可能仍为已通过) */
|
||||||
business_prescription_audit_rejected: 0,
|
business_prescription_audit_rejected: 0,
|
||||||
business_prescription_audit_remark: '',
|
business_prescription_audit_remark: '',
|
||||||
times_per_day: 2
|
times_per_day: 2,
|
||||||
|
aux_usage: defaultAuxUsage('浓缩水丸')
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 编辑页:关联业务订单上的服用天数、医助备注(与处方提示一致) */
|
/** 编辑页:关联业务订单上的服用天数、医助备注(与处方提示一致) */
|
||||||
@@ -2459,6 +2895,7 @@ watch(() => editForm.prescription_type, (newType) => {
|
|||||||
editForm.need_decoction = false
|
editForm.need_decoction = false
|
||||||
editForm.bags_per_dose = 1
|
editForm.bags_per_dose = 1
|
||||||
}
|
}
|
||||||
|
Object.assign(editForm.aux_usage, defaultAuxUsage(newType))
|
||||||
})
|
})
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
@@ -2687,15 +3124,15 @@ function slipAgeText(age: unknown) {
|
|||||||
return `${age}岁`
|
return `${age}岁`
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSlipHerbs(raw: unknown): Array<{ name: string; dosage: number }> {
|
function herbValidationLabel(globalIndex: number): string {
|
||||||
if (!raw) return []
|
const herb = editForm.herbs[globalIndex]
|
||||||
if (Array.isArray(raw)) {
|
if (!herb) return `第${globalIndex + 1}味`
|
||||||
return raw.map((x: any) => ({
|
const ft = normalizeFormulaType(herb.formula_type)
|
||||||
name: String(x?.name ?? '').trim(),
|
let n = 0
|
||||||
dosage: Number(x?.dosage) || 0
|
for (let i = 0; i <= globalIndex; i++) {
|
||||||
}))
|
if (normalizeFormulaType(editForm.herbs[i]?.formula_type) === ft) n++
|
||||||
}
|
}
|
||||||
return []
|
return `${ft}第${n}味`
|
||||||
}
|
}
|
||||||
|
|
||||||
function listRxOrderWarnings(row: any): string[] {
|
function listRxOrderWarnings(row: any): string[] {
|
||||||
@@ -2918,6 +3355,8 @@ const showLibraryDialog = ref(false)
|
|||||||
const libraryLoading = ref(false)
|
const libraryLoading = ref(false)
|
||||||
const libraryList = ref<any[]>([])
|
const libraryList = ref<any[]>([])
|
||||||
const librarySearchName = ref('')
|
const librarySearchName = ref('')
|
||||||
|
const librarySearchFormulaType = ref('')
|
||||||
|
const libraryImportMode = ref<'replace' | 'append'>('replace')
|
||||||
const libraryPage = ref(1)
|
const libraryPage = ref(1)
|
||||||
const libraryPageSize = ref(15)
|
const libraryPageSize = ref(15)
|
||||||
const libraryTotal = ref(0)
|
const libraryTotal = ref(0)
|
||||||
@@ -2962,8 +3401,8 @@ const loadLibraryList = async () => {
|
|||||||
page_no: libraryPage.value,
|
page_no: libraryPage.value,
|
||||||
page_size: libraryPageSize.value,
|
page_size: libraryPageSize.value,
|
||||||
prescription_name: librarySearchName.value,
|
prescription_name: librarySearchName.value,
|
||||||
is_public: '',
|
formula_type: librarySearchFormulaType.value,
|
||||||
creator_id: doctorId
|
prescribing_creator_id: doctorId
|
||||||
})
|
})
|
||||||
libraryList.value = res?.lists || []
|
libraryList.value = res?.lists || []
|
||||||
libraryTotal.value = res?.count || 0
|
libraryTotal.value = res?.count || 0
|
||||||
@@ -2998,18 +3437,33 @@ const handleImportLibrary = (row: any) => {
|
|||||||
feedback.msgWarning('该处方没有药材信息')
|
feedback.msgWarning('该处方没有药材信息')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const imported = JSON.parse(JSON.stringify(row.herbs)) as Array<{ name: string; dosage: number }>
|
const formulaType = normalizeFormulaType(row.formula_type)
|
||||||
editForm.herbs = imported
|
const imported = (JSON.parse(JSON.stringify(row.herbs)) as any[]).map((h) => ({
|
||||||
|
...normalizeHerbRow(h),
|
||||||
|
formula_type: formulaType
|
||||||
|
}))
|
||||||
|
if (libraryImportMode.value === 'replace') {
|
||||||
|
const kept = editForm.herbs.filter((h) => normalizeFormulaType(h.formula_type) !== formulaType)
|
||||||
|
editForm.herbs = [...kept, ...imported]
|
||||||
|
} else {
|
||||||
|
editForm.herbs.push(...imported)
|
||||||
|
}
|
||||||
|
const modeHint = libraryImportMode.value === 'append' ? '(已追加)' : ''
|
||||||
if (findDuplicateHerbNamesLocal().length) {
|
if (findDuplicateHerbNamesLocal().length) {
|
||||||
feedback.msgWarning('导入的处方中存在重复药名,请合并剂量或删除多余行')
|
feedback.msgWarning(
|
||||||
|
`已导入处方「${row.prescription_name}」${modeHint},共${imported.length}味药材。存在重复药名,请合并剂量或删除多余行`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
feedback.msgSuccess(`已导入处方「${row.prescription_name}」${modeHint},共${imported.length}味药材`)
|
||||||
}
|
}
|
||||||
feedback.msgSuccess(`已导入处方「${row.prescription_name}」,共${imported.length}味药材`)
|
|
||||||
showLibraryDialog.value = false
|
showLibraryDialog.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(showLibraryDialog, (open) => {
|
watch(showLibraryDialog, (open) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
librarySearchName.value = ''
|
librarySearchName.value = ''
|
||||||
|
librarySearchFormulaType.value = ''
|
||||||
|
libraryImportMode.value = 'replace'
|
||||||
libraryPage.value = 1
|
libraryPage.value = 1
|
||||||
loadLibraryList()
|
loadLibraryList()
|
||||||
}
|
}
|
||||||
@@ -3141,7 +3595,7 @@ async function handlePasteRecipeImport() {
|
|||||||
}
|
}
|
||||||
pasteRecipeImportLoading.value = true
|
pasteRecipeImportLoading.value = true
|
||||||
try {
|
try {
|
||||||
const resolved: Array<{ name: string; dosage: number }> = []
|
const resolved: HerbRow[] = []
|
||||||
const skippedNames: string[] = []
|
const skippedNames: string[] = []
|
||||||
for (const row of parsed) {
|
for (const row of parsed) {
|
||||||
const name = await resolvePasteHerbNameFromLibrary(row.name)
|
const name = await resolvePasteHerbNameFromLibrary(row.name)
|
||||||
@@ -3149,7 +3603,7 @@ async function handlePasteRecipeImport() {
|
|||||||
skippedNames.push(row.name.trim())
|
skippedNames.push(row.name.trim())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
resolved.push({ name, dosage: row.dosage })
|
resolved.push({ name, dosage: row.dosage, formula_type: '主方' })
|
||||||
}
|
}
|
||||||
const skippedUnique = [...new Set(skippedNames.filter(Boolean))]
|
const skippedUnique = [...new Set(skippedNames.filter(Boolean))]
|
||||||
if (resolved.length === 0) {
|
if (resolved.length === 0) {
|
||||||
@@ -3161,7 +3615,8 @@ async function handlePasteRecipeImport() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (pasteRecipeImportMode.value === 'replace') {
|
if (pasteRecipeImportMode.value === 'replace') {
|
||||||
editForm.herbs = resolved
|
const auxKept = editForm.herbs.filter((h) => normalizeFormulaType(h.formula_type) === '辅方')
|
||||||
|
editForm.herbs = [...auxKept, ...resolved]
|
||||||
} else {
|
} else {
|
||||||
editForm.herbs.push(...resolved)
|
editForm.herbs.push(...resolved)
|
||||||
}
|
}
|
||||||
@@ -3186,11 +3641,12 @@ async function handlePasteRecipeImport() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加药材
|
// 添加药材(主方 / 辅方)
|
||||||
const addHerb = () => {
|
const addHerb = (formulaType: FormulaType = '主方') => {
|
||||||
editForm.herbs.push({
|
editForm.herbs.push({
|
||||||
name: '',
|
name: '',
|
||||||
dosage: 0
|
dosage: 0,
|
||||||
|
formula_type: formulaType
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3240,6 +3696,8 @@ const resetForm = () => {
|
|||||||
editForm.is_system_auto = 0
|
editForm.is_system_auto = 0
|
||||||
editForm.business_prescription_audit_rejected = 0
|
editForm.business_prescription_audit_rejected = 0
|
||||||
editForm.business_prescription_audit_remark = ''
|
editForm.business_prescription_audit_remark = ''
|
||||||
|
editForm.times_per_day = 2
|
||||||
|
Object.assign(editForm.aux_usage, defaultAuxUsage('浓缩水丸'))
|
||||||
clearRxLinkedOrderHint()
|
clearRxLinkedOrderHint()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3338,7 +3796,7 @@ const handleEdit = async (row: any) => {
|
|||||||
editForm.pulse = src.pulse || ''
|
editForm.pulse = src.pulse || ''
|
||||||
editForm.pulse_condition = src.pulse_condition || ''
|
editForm.pulse_condition = src.pulse_condition || ''
|
||||||
editForm.clinical_diagnosis = src.clinical_diagnosis || ''
|
editForm.clinical_diagnosis = src.clinical_diagnosis || ''
|
||||||
editForm.herbs = src.herbs ? JSON.parse(JSON.stringify(src.herbs)) : []
|
editForm.herbs = src.herbs ? (src.herbs as any[]).map((h) => normalizeHerbRow(h)) : []
|
||||||
editForm.dose_count = src.dose_count ?? 7
|
editForm.dose_count = src.dose_count ?? 7
|
||||||
editForm.dose_unit = src.dose_unit || '剂'
|
editForm.dose_unit = src.dose_unit || '剂'
|
||||||
editForm.usage_days = src.usage_days ?? 7
|
editForm.usage_days = src.usage_days ?? 7
|
||||||
@@ -3363,6 +3821,10 @@ const handleEdit = async (row: any) => {
|
|||||||
editForm.is_system_auto = Number(src.is_system_auto) === 1 ? 1 : 0
|
editForm.is_system_auto = Number(src.is_system_auto) === 1 ? 1 : 0
|
||||||
editForm.business_prescription_audit_rejected = Number(src.business_prescription_audit_rejected) === 1 ? 1 : 0
|
editForm.business_prescription_audit_rejected = Number(src.business_prescription_audit_rejected) === 1 ? 1 : 0
|
||||||
editForm.business_prescription_audit_remark = String(src.business_prescription_audit_remark || '')
|
editForm.business_prescription_audit_remark = String(src.business_prescription_audit_remark || '')
|
||||||
|
Object.assign(
|
||||||
|
editForm.aux_usage,
|
||||||
|
normalizeAuxUsageForm(src.aux_usage, editForm.prescription_type)
|
||||||
|
)
|
||||||
|
|
||||||
// 数据加载完成,重新启用watch
|
// 数据加载完成,重新启用watch
|
||||||
// 使用 setTimeout 确保所有数据都已经渲染完成
|
// 使用 setTimeout 确保所有数据都已经渲染完成
|
||||||
@@ -3397,12 +3859,13 @@ const handleSubmit = async () => {
|
|||||||
|
|
||||||
for (let i = 0; i < editForm.herbs.length; i++) {
|
for (let i = 0; i < editForm.herbs.length; i++) {
|
||||||
const herb = editForm.herbs[i]
|
const herb = editForm.herbs[i]
|
||||||
|
const label = herbValidationLabel(i)
|
||||||
if (!herb.name || !herb.name.trim()) {
|
if (!herb.name || !herb.name.trim()) {
|
||||||
feedback.msgError(`第${i + 1}味药材名称不能为空`)
|
feedback.msgError(`${label}药材名称不能为空`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!herb.dosage || herb.dosage <= 0) {
|
if (!herb.dosage || herb.dosage <= 0) {
|
||||||
feedback.msgError(`第${i + 1}味药材剂量必须大于0`)
|
feedback.msgError(`${label}药材剂量必须大于0`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3826,6 +4289,38 @@ onMounted(async () => {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rx-herb-section-label {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #409eff;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rx-herb-section-label--aux {
|
||||||
|
color: #e6a23c;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.herb-formula-block__head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-formula-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 8px 0 4px;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-formula-title--aux {
|
||||||
|
color: #e6a23c;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.rx-herb-cell {
|
.rx-herb-cell {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 64px;
|
grid-template-columns: 1fr 64px;
|
||||||
|
|||||||
@@ -11,6 +11,13 @@
|
|||||||
@keyup.enter="resetPage"
|
@keyup.enter="resetPage"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item class="w-[200px]" label="处方类型">
|
||||||
|
<el-select v-model="formData.formula_type" placeholder="全部" clearable>
|
||||||
|
<el-option label="全部" :value="''" />
|
||||||
|
<el-option label="主方" value="主方" />
|
||||||
|
<el-option label="辅方" value="辅方" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item class="w-[200px]" label="是否公开">
|
<el-form-item class="w-[200px]" label="是否公开">
|
||||||
<el-select v-model="formData.is_public" placeholder="全部" clearable>
|
<el-select v-model="formData.is_public" placeholder="全部" clearable>
|
||||||
<el-option label="全部" :value="''" />
|
<el-option label="全部" :value="''" />
|
||||||
@@ -37,6 +44,13 @@
|
|||||||
<el-table :data="pager.lists" size="large">
|
<el-table :data="pager.lists" size="large">
|
||||||
<el-table-column label="ID" prop="id" min-width="60" />
|
<el-table-column label="ID" prop="id" min-width="60" />
|
||||||
<el-table-column label="处方名称" prop="prescription_name" min-width="200" show-overflow-tooltip />
|
<el-table-column label="处方名称" prop="prescription_name" min-width="200" show-overflow-tooltip />
|
||||||
|
<el-table-column label="处方类型" min-width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.formula_type === '辅方' ? 'warning' : 'primary'" size="small">
|
||||||
|
{{ row.formula_type || '主方' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="药材数量" min-width="100">
|
<el-table-column label="药材数量" min-width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ row.herbs?.length || 0 }}味
|
{{ row.herbs?.length || 0 }}味
|
||||||
@@ -115,6 +129,13 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="处方类型" prop="formula_type">
|
||||||
|
<el-radio-group v-model="editForm.formula_type">
|
||||||
|
<el-radio value="主方">主方</el-radio>
|
||||||
|
<el-radio value="辅方">辅方</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="药材配方" prop="herbs" required>
|
<el-form-item label="药材配方" prop="herbs" required>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<el-button
|
<el-button
|
||||||
@@ -200,6 +221,7 @@ import MedicineNameSelect from '@/components/medicine-name-select/index.vue'
|
|||||||
// 表单数据
|
// 表单数据
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
prescription_name: '',
|
prescription_name: '',
|
||||||
|
formula_type: '',
|
||||||
is_public: ''
|
is_public: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -212,6 +234,7 @@ const formRef = ref<FormInstance>()
|
|||||||
const editForm = reactive({
|
const editForm = reactive({
|
||||||
id: 0,
|
id: 0,
|
||||||
prescription_name: '',
|
prescription_name: '',
|
||||||
|
formula_type: '主方',
|
||||||
herbs: [] as Array<{ name: string; dosage: number }>,
|
herbs: [] as Array<{ name: string; dosage: number }>,
|
||||||
is_public: 0
|
is_public: 0
|
||||||
})
|
})
|
||||||
@@ -267,6 +290,7 @@ const removeHerb = (index: number) => {
|
|||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
editForm.id = 0
|
editForm.id = 0
|
||||||
editForm.prescription_name = ''
|
editForm.prescription_name = ''
|
||||||
|
editForm.formula_type = '主方'
|
||||||
editForm.herbs = []
|
editForm.herbs = []
|
||||||
editForm.is_public = 0
|
editForm.is_public = 0
|
||||||
}
|
}
|
||||||
@@ -283,6 +307,7 @@ const handleView = (row: any) => {
|
|||||||
editMode.value = 'view'
|
editMode.value = 'view'
|
||||||
editForm.id = row.id
|
editForm.id = row.id
|
||||||
editForm.prescription_name = row.prescription_name || ''
|
editForm.prescription_name = row.prescription_name || ''
|
||||||
|
editForm.formula_type = row.formula_type || '主方'
|
||||||
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
||||||
editForm.is_public = row.is_public ?? 0
|
editForm.is_public = row.is_public ?? 0
|
||||||
showEdit.value = true
|
showEdit.value = true
|
||||||
@@ -293,6 +318,7 @@ const handleEdit = (row: any) => {
|
|||||||
editMode.value = 'edit'
|
editMode.value = 'edit'
|
||||||
editForm.id = row.id
|
editForm.id = row.id
|
||||||
editForm.prescription_name = row.prescription_name || ''
|
editForm.prescription_name = row.prescription_name || ''
|
||||||
|
editForm.formula_type = row.formula_type || '主方'
|
||||||
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
||||||
editForm.is_public = row.is_public ?? 0
|
editForm.is_public = row.is_public ?? 0
|
||||||
showEdit.value = true
|
showEdit.value = true
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3047,19 +3047,57 @@ function formatTime(v: unknown) {
|
|||||||
return String(v)
|
return String(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 与 server/config/project.php order_edit_all_roles 一致 */
|
||||||
|
const ORDER_EDIT_ALL_ROLE_IDS = [0, 3]
|
||||||
|
|
||||||
|
function canBypassCreatorDualAuditEditLock(): boolean {
|
||||||
|
const u = userStore.userInfo
|
||||||
|
if (!u) return false
|
||||||
|
if (Number(u.root) === 1) return true
|
||||||
|
const ids = Array.isArray(u.role_ids) ? u.role_ids.map((n: unknown) => Number(n)) : []
|
||||||
|
return ids.some((id) => ORDER_EDIT_ALL_ROLE_IDS.includes(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCurrentUserOrderCreator(row: { creator_id?: number }) {
|
||||||
|
const uid = Number(userStore.userInfo?.id)
|
||||||
|
return uid > 0 && Number(row.creator_id) === uid
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDualAuditPassed(row: {
|
||||||
|
prescription_audit_status?: number
|
||||||
|
payment_slip_audit_status?: number
|
||||||
|
}) {
|
||||||
|
return Number(row.prescription_audit_status) === 1 && Number(row.payment_slip_audit_status) === 1
|
||||||
|
}
|
||||||
|
|
||||||
function canEditRow(row: {
|
function canEditRow(row: {
|
||||||
|
creator_id?: number
|
||||||
fulfillment_status?: number
|
fulfillment_status?: number
|
||||||
|
prescription_audit_status?: number
|
||||||
|
payment_slip_audit_status?: number
|
||||||
gancao_reciperl_order_no?: string | null
|
gancao_reciperl_order_no?: string | null
|
||||||
gancao_submit_time?: number | null
|
gancao_submit_time?: number | null
|
||||||
}) {
|
}) {
|
||||||
// 已发货(5)、已签收(6)、已完成(3)、已取消(4) 不可编辑
|
|
||||||
const fs = Number(row.fulfillment_status)
|
const fs = Number(row.fulfillment_status)
|
||||||
if (fs !== 1 && fs !== 2) return false
|
if (fs === 3 || fs === 4) return false
|
||||||
// 已成功提交甘草(已生成甘草处方单号 或 提交时间 > 0)不允许再编辑
|
|
||||||
|
if (
|
||||||
|
isCurrentUserOrderCreator(row) &&
|
||||||
|
isDualAuditPassed(row) &&
|
||||||
|
!canBypassCreatorDualAuditEditLock()
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const gcNo = String(row.gancao_reciperl_order_no || '').trim()
|
const gcNo = String(row.gancao_reciperl_order_no || '').trim()
|
||||||
const gcTime = Number(row.gancao_submit_time || 0)
|
const gcTime = Number(row.gancao_submit_time || 0)
|
||||||
if (gcNo !== '' || gcTime > 0) return false
|
const gcLocked = gcNo !== '' || gcTime > 0
|
||||||
return true
|
|
||||||
|
if (gcLocked) {
|
||||||
|
return [1, 2, 5, 7].includes(fs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs === 1 || fs === 2
|
||||||
}
|
}
|
||||||
|
|
||||||
function canRxAudit(row: { prescription_audit_status?: number; fulfillment_status?: number }) {
|
function canRxAudit(row: { prescription_audit_status?: number; fulfillment_status?: number }) {
|
||||||
@@ -3128,9 +3166,7 @@ function canCompleteRow(row: { fulfillment_status?: number; payment_slip_audit_s
|
|||||||
}
|
}
|
||||||
|
|
||||||
function canQuickTrackRow(row: { fulfillment_status?: number }) {
|
function canQuickTrackRow(row: { fulfillment_status?: number }) {
|
||||||
// 已发货(5) / 已签收(6) 后如需修改快递信息可单独使用此功能
|
return Number(row.fulfillment_status) === 5
|
||||||
const fs = Number(row.fulfillment_status)
|
|
||||||
return fs === 5 || fs === 6
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function canUploadGancaoRow(row: {
|
function canUploadGancaoRow(row: {
|
||||||
@@ -3404,6 +3440,40 @@ const logisticsTraceLoading = ref(false)
|
|||||||
const logisticsTracePayload = ref<Record<string, any> | null>(null)
|
const logisticsTracePayload = ref<Record<string, any> | null>(null)
|
||||||
/** 顺丰/快递100:与运单一致的收件手机后四位(可覆盖订单收货手机) */
|
/** 顺丰/快递100:与运单一致的收件手机后四位(可覆盖订单收货手机) */
|
||||||
const logisticsTracePhoneTail = ref('')
|
const logisticsTracePhoneTail = ref('')
|
||||||
|
/** 物流轨迹请求序号:同手机多运单或快速切换订单时,丢弃过期的异步响应 */
|
||||||
|
let logisticsTraceRequestSeq = 0
|
||||||
|
|
||||||
|
type LogisticsTraceRequestContext = {
|
||||||
|
requestSeq: number
|
||||||
|
orderId: number
|
||||||
|
trackingNumber: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function bumpLogisticsTraceRequestToken() {
|
||||||
|
logisticsTraceRequestSeq += 1
|
||||||
|
logisticsTracePayload.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCurrentLogisticsTraceContext(ctx: LogisticsTraceRequestContext): boolean {
|
||||||
|
if (ctx.requestSeq !== logisticsTraceRequestSeq) return false
|
||||||
|
if (Number(detailData.value?.id) !== ctx.orderId) return false
|
||||||
|
return String(detailData.value?.tracking_number || '').trim() === ctx.trackingNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLogisticsTracePayload(
|
||||||
|
res: unknown,
|
||||||
|
expectedTrackingNumber: string,
|
||||||
|
expectedOrderId: number
|
||||||
|
): Record<string, any> | null {
|
||||||
|
const raw = (res as { data?: unknown })?.data ?? res
|
||||||
|
if (!raw || typeof raw !== 'object') return null
|
||||||
|
const payload = raw as Record<string, any>
|
||||||
|
const respNum = String(payload.tracking_number || '').trim()
|
||||||
|
if (respNum && respNum !== expectedTrackingNumber) return null
|
||||||
|
const respOrderId = Number(payload.order_id)
|
||||||
|
if (respOrderId > 0 && respOrderId !== expectedOrderId) return null
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
const detailLogs = ref<any[]>([])
|
const detailLogs = ref<any[]>([])
|
||||||
const updateAmountVisible = ref(false)
|
const updateAmountVisible = ref(false)
|
||||||
@@ -3498,24 +3568,33 @@ function expressCompanyLabel(v: unknown) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchLogisticsTrace() {
|
async function fetchLogisticsTrace() {
|
||||||
const id = Number(detailData.value?.id)
|
const orderId = Number(detailData.value?.id)
|
||||||
if (!id) return
|
const trackingNumber = String(detailData.value?.tracking_number || '').trim()
|
||||||
|
if (!orderId || !trackingNumber) return
|
||||||
|
|
||||||
|
const requestSeq = ++logisticsTraceRequestSeq
|
||||||
|
const ctx: LogisticsTraceRequestContext = { requestSeq, orderId, trackingNumber }
|
||||||
|
|
||||||
logisticsTraceLoading.value = true
|
logisticsTraceLoading.value = true
|
||||||
try {
|
try {
|
||||||
const digits = String(logisticsTracePhoneTail.value || '').replace(/\D/g, '')
|
const digits = String(logisticsTracePhoneTail.value || '').replace(/\D/g, '')
|
||||||
const params: { id: number; express_company?: string; phone_tail?: string } = {
|
const params: { id: number; express_company?: string; phone_tail?: string } = {
|
||||||
id,
|
id: orderId,
|
||||||
express_company: detailLogisticsExpress.value
|
express_company: detailLogisticsExpress.value
|
||||||
}
|
}
|
||||||
if (digits.length >= 4) {
|
if (digits.length >= 4) {
|
||||||
params.phone_tail = digits
|
params.phone_tail = digits
|
||||||
}
|
}
|
||||||
const res: any = await prescriptionOrderLogisticsTrace(params)
|
const res: any = await prescriptionOrderLogisticsTrace(params)
|
||||||
logisticsTracePayload.value = (res?.data ?? res) as Record<string, any>
|
if (!isCurrentLogisticsTraceContext(ctx)) return
|
||||||
|
logisticsTracePayload.value = parseLogisticsTracePayload(res, trackingNumber, orderId)
|
||||||
} catch {
|
} catch {
|
||||||
|
if (!isCurrentLogisticsTraceContext(ctx)) return
|
||||||
logisticsTracePayload.value = null
|
logisticsTracePayload.value = null
|
||||||
} finally {
|
} finally {
|
||||||
logisticsTraceLoading.value = false
|
if (isCurrentLogisticsTraceContext(ctx)) {
|
||||||
|
logisticsTraceLoading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3641,7 +3720,7 @@ async function openDetail(id: number) {
|
|||||||
// 修复 Bug:显式彻底清空缓存,防止前一次弹窗的数据残留
|
// 修复 Bug:显式彻底清空缓存,防止前一次弹窗的数据残留
|
||||||
detailData.value = null
|
detailData.value = null
|
||||||
detailUnlinkedPayOrders.value = []
|
detailUnlinkedPayOrders.value = []
|
||||||
logisticsTracePayload.value = null
|
bumpLogisticsTraceRequestToken()
|
||||||
detailLogisticsExpress.value = 'auto'
|
detailLogisticsExpress.value = 'auto'
|
||||||
logisticsTracePhoneTail.value = ''
|
logisticsTracePhoneTail.value = ''
|
||||||
detailLogs.value = []
|
detailLogs.value = []
|
||||||
@@ -3657,7 +3736,7 @@ async function openDetail(id: number) {
|
|||||||
const dig = String(d.recipient_phone || '').replace(/\D/g, '')
|
const dig = String(d.recipient_phone || '').replace(/\D/g, '')
|
||||||
logisticsTracePhoneTail.value = dig.length >= 4 ? dig : ''
|
logisticsTracePhoneTail.value = dig.length >= 4 ? dig : ''
|
||||||
if (String(d.tracking_number || '').trim()) {
|
if (String(d.tracking_number || '').trim()) {
|
||||||
fetchLogisticsTrace()
|
void fetchLogisticsTrace()
|
||||||
}
|
}
|
||||||
fetchLogs(id)
|
fetchLogs(id)
|
||||||
|
|
||||||
@@ -3887,7 +3966,25 @@ async function goEditOrderNextStep() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openEdit(row: { id: number }) {
|
async function openEdit(row: {
|
||||||
|
id: number
|
||||||
|
creator_id?: number
|
||||||
|
prescription_audit_status?: number
|
||||||
|
payment_slip_audit_status?: number
|
||||||
|
fulfillment_status?: number
|
||||||
|
gancao_reciperl_order_no?: string | null
|
||||||
|
gancao_submit_time?: number | null
|
||||||
|
}) {
|
||||||
|
if (!canEditRow(row)) {
|
||||||
|
if (
|
||||||
|
isCurrentUserOrderCreator(row) &&
|
||||||
|
isDualAuditPassed(row) &&
|
||||||
|
!canBypassCreatorDualAuditEditLock()
|
||||||
|
) {
|
||||||
|
feedback.msgWarning('处方与支付单均已审核通过,创建人不可再编辑')
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
editOrderStep.value = 0
|
editOrderStep.value = 0
|
||||||
editOrderPrescription.value = null
|
editOrderPrescription.value = null
|
||||||
editVisible.value = true
|
editVisible.value = true
|
||||||
@@ -4312,8 +4409,8 @@ async function submitQuickTrack() {
|
|||||||
if (nd) detailData.value = nd
|
if (nd) detailData.value = nd
|
||||||
} catch { /* 静默 */ }
|
} catch { /* 静默 */ }
|
||||||
}
|
}
|
||||||
// 履约中状态且刚填单号,提示是否立即发货
|
// 仅保存前为履约中(2)时才询问确认发货;已发货(5)/已签收(6)等修改单号不再弹窗
|
||||||
if (canShipRow({ fulfillment_status: 2, tracking_number: quickTrackForm.tracking_number })) {
|
if (canShipRow({ fulfillment_status: Number(d.fulfillment_status) })) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await ElMessageBox.confirm(
|
||||||
`快递单号「${quickTrackForm.tracking_number}」已保存,是否立即确认发货?`,
|
`快递单号「${quickTrackForm.tracking_number}」已保存,是否立即确认发货?`,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -102,6 +102,18 @@
|
|||||||
@keyup.enter="resetPage"
|
@keyup.enter="resetPage"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="添加时间">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="addTimeRange"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
clearable
|
||||||
|
class="!w-[280px]"
|
||||||
|
@change="onAddTimeRangeChange"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="标签">
|
<el-form-item label="标签">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="queryParams.tag_ids"
|
v-model="queryParams.tag_ids"
|
||||||
@@ -557,12 +569,34 @@ const syncSettings = reactive({
|
|||||||
interval: 3600
|
interval: 3600
|
||||||
})
|
})
|
||||||
|
|
||||||
const queryParams = reactive<{ name: string; follow_user: string; tag_ids: string[] }>({
|
const queryParams = reactive<{
|
||||||
|
name: string
|
||||||
|
follow_user: string
|
||||||
|
tag_ids: string[]
|
||||||
|
add_time_start: string
|
||||||
|
add_time_end: string
|
||||||
|
}>({
|
||||||
name: '',
|
name: '',
|
||||||
follow_user: '',
|
follow_user: '',
|
||||||
tag_ids: []
|
tag_ids: [],
|
||||||
|
add_time_start: '',
|
||||||
|
add_time_end: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 与列表「添加时间」列口径一致(库内 external_first_add_time / create_time) */
|
||||||
|
const addTimeRange = ref<[string, string] | null>(null)
|
||||||
|
|
||||||
|
function onAddTimeRangeChange(val: [string, string] | null) {
|
||||||
|
if (val && val.length === 2 && val[0] && val[1]) {
|
||||||
|
queryParams.add_time_start = val[0]
|
||||||
|
queryParams.add_time_end = val[1]
|
||||||
|
} else {
|
||||||
|
queryParams.add_time_start = ''
|
||||||
|
queryParams.add_time_end = ''
|
||||||
|
}
|
||||||
|
resetPage()
|
||||||
|
}
|
||||||
|
|
||||||
// ── 标签维度(筛选下拉 + 抽屉面板共用同一份数据) ──────────────────────────
|
// ── 标签维度(筛选下拉 + 抽屉面板共用同一份数据) ──────────────────────────
|
||||||
interface TagItem {
|
interface TagItem {
|
||||||
tag_id: string
|
tag_id: string
|
||||||
@@ -793,6 +827,9 @@ function handleReset() {
|
|||||||
queryParams.name = ''
|
queryParams.name = ''
|
||||||
queryParams.follow_user = ''
|
queryParams.follow_user = ''
|
||||||
queryParams.tag_ids = []
|
queryParams.tag_ids = []
|
||||||
|
queryParams.add_time_start = ''
|
||||||
|
queryParams.add_time_end = ''
|
||||||
|
addTimeRange.value = null
|
||||||
resetParams()
|
resetParams()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -290,12 +290,12 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="yeji-table__foot yeji-table__foot--left">
|
<div class="yeji-table__foot yeji-table__foot--left">
|
||||||
<span>诊金单位:元 · 被指派数与部门业绩表同口径 · 接诊率 = 诊金 ÷ 进线(元/进线)</span>
|
<span>诊金单位:元 · 诊金/接诊诊单/复诊均按「订单创建人 = 该医助」归属,与部门「合计业绩 / 接诊诊单 / 复诊」同口径 · 被指派数与部门业绩表同口径 · 接诊率 = 诊金 ÷ 进线(元/进线)</span>
|
||||||
<span v-if="lb.er_center_subtree">
|
<span v-if="lb.er_center_subtree">
|
||||||
· 复诊列为二中心口径(同一诊单业务单序列第 2 笔起),点击数字查看对应业务订单
|
· 复诊列为二中心口径(同一诊单业务单序列第 2 笔起),点击数字查看对应业务订单
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
· 预约诊单:预约日期在统计区间内,状态含已预约/已完成/已过号(不含已取消),医助归属与接诊单数一致;各科组展示全部医助。
|
· 预约诊单:预约日期在统计区间内,状态含已预约/已完成/已过号(不含已取消),医助归属与接诊单数一致(按挂号 assistant_id 优先,否则诊单 assistant_id);各科组展示全部医助。
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -370,7 +370,7 @@
|
|||||||
<el-tooltip placement="top" effect="dark" :show-after="200">
|
<el-tooltip placement="top" effect="dark" :show-after="200">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div style="max-width: 320px; line-height: 1.7; font-size: 12px">
|
<div style="max-width: 320px; line-height: 1.7; font-size: 12px">
|
||||||
仅<b>名称含「二中心」</b>的部门及其组织下级:与本表区间一致,按患者业务单 <b>create_time</b> 排序,<b>第 1 笔不计</b>;第 2 笔起计入合计,并分列复诊2、复诊三…(含本部门及下级汇总)。订单条件同「合计业绩」列(非取消)。非二中心子树部门为 0。
|
仅<b>名称含「二中心」</b>的部门及其组织下级:候选订单为有诊单医助、且 <b>fulfillment_status</b> 非 4/9/10(<b>NULL 计入</b>)的业务单,按患者 <b>create_time</b> 排序,<b>第 1 笔不计</b>;第 2 笔起计入合计,分列复诊2、复诊三…。<b>归属</b>按订单创建人的人事部门,与<b>合计业绩</b>列同口径。非二中心子树部门为 0。
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-icon class="col-info"><InfoFilled /></el-icon>
|
<el-icon class="col-info"><InfoFilled /></el-icon>
|
||||||
@@ -395,8 +395,8 @@
|
|||||||
<el-tooltip placement="top" effect="dark" :show-after="200">
|
<el-tooltip placement="top" effect="dark" :show-after="200">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div style="max-width: 300px; line-height: 1.7; font-size: 12px">
|
<div style="max-width: 300px; line-height: 1.7; font-size: 12px">
|
||||||
按订单<b>创建时间</b>统计(剔除履约已取消),金额与<b>接诊诊单</b>列一致:<b>创建人</b>人事部门优先,无创建人时回退诊单<b>医助</b>;落在本展示行(含下级)即计入,同表多行命中时取最深的展示部门。<br />
|
按订单<b>创建时间</b>统计(剔除履约已取消/拒收/退款 4·9·10),金额与<b>接诊诊单</b>列一致:仅按<b>订单创建人</b>的人事部门落在本展示行(含下级)计入,多行命中时取最深的展示部门。与<b>医助排行榜诊金</b>同口径。<br />
|
||||||
<b>不受渠道筛选影响</b>。与侧栏按部门打开的业务订单列表可对齐。<br />
|
<b>不受渠道筛选影响</b>。与侧栏按部门打开的业务订单列表对齐。<br />
|
||||||
点击金额可查看对应业务订单明细(侧栏默认「合计业绩」全量列表)。
|
点击金额可查看对应业务订单明细(侧栏默认「合计业绩」全量列表)。
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -409,7 +409,7 @@
|
|||||||
<template #content>
|
<template #content>
|
||||||
<div style="max-width: 340px; line-height: 1.7; font-size: 12px">
|
<div style="max-width: 340px; line-height: 1.7; font-size: 12px">
|
||||||
「{{ tb.channel_name }}」渠道业绩(与卡片标题渠道一致)。与「处方订单列表」业绩<b>同口径</b>:按订单
|
「{{ tb.channel_name }}」渠道业绩(与卡片标题渠道一致)。与「处方订单列表」业绩<b>同口径</b>:按订单
|
||||||
<b>create_time</b> 归日,剔除履约已取消(4)。<br />
|
<b>create_time</b> 归日,剔除履约状态 4(已取消)/9(拒收)/10(退款)。<br />
|
||||||
渠道判定用 EXISTS:该患者有任一挂号 <b>channels 命中字典 + status=3</b>(不限挂号时点)。<br />
|
渠道判定用 EXISTS:该患者有任一挂号 <b>channels 命中字典 + status=3</b>(不限挂号时点)。<br />
|
||||||
点击金额可查看该部门本渠道订单明细(侧栏「渠道筛选」列表)。
|
点击金额可查看该部门本渠道订单明细(侧栏「渠道筛选」列表)。
|
||||||
</div>
|
</div>
|
||||||
@@ -445,8 +445,12 @@
|
|||||||
<el-tooltip placement="top" effect="dark" :show-after="200">
|
<el-tooltip placement="top" effect="dark" :show-after="200">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div style="max-width: 320px; line-height: 1.7; font-size: 12px">
|
<div style="max-width: 320px; line-height: 1.7; font-size: 12px">
|
||||||
<b>业务订单条数</b>(非取消):订单 <b>create_time</b> 落入区间、<b>fulfillment_status ≠ 4</b>;与列表筛选
|
<<<<<<< HEAD
|
||||||
|
<b>业务订单条数</b>(计业绩):订单 <b>create_time</b> 落入区间、<b>fulfillment_status ∉ {4,9,10}</b>(<b>NULL 计入</b>);按<b>订单创建人</b>的人事部门落在该部门子树即计入(表格多行命中时取最深的展示部门)。与<b>合计业绩 / 医助排行榜接诊诊单</b>同口径。选定渠道时本列仍为全量。
|
||||||
|
=======
|
||||||
|
<b>业务订单条数</b>(计业绩):订单 <b>create_time</b> 落入区间、<b>fulfillment_status ∉ {4,9,10}</b>;与列表筛选
|
||||||
<b>assistant_dept_id</b> 时一致——<b>创建人</b>人事部门优先,无创建人则诊单 <b>医助</b>;落在该部门子树即计入(表格多行命中时取最深的展示部门)。与侧栏勾选「与表格业绩对齐」时的集合可能略有差异。选定渠道时本列仍为全量。
|
<b>assistant_dept_id</b> 时一致——<b>创建人</b>人事部门优先,无创建人则诊单 <b>医助</b>;落在该部门子树即计入(表格多行命中时取最深的展示部门)。与侧栏勾选「与表格业绩对齐」时的集合可能略有差异。选定渠道时本列仍为全量。
|
||||||
|
>>>>>>> master
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-icon class="col-info"><InfoFilled /></el-icon>
|
<el-icon class="col-info"><InfoFilled /></el-icon>
|
||||||
@@ -457,7 +461,7 @@
|
|||||||
<el-tooltip placement="top" effect="dark" :show-after="200">
|
<el-tooltip placement="top" effect="dark" :show-after="200">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div style="max-width: 320px; line-height: 1.7; font-size: 12px">
|
<div style="max-width: 320px; line-height: 1.7; font-size: 12px">
|
||||||
「{{ tb.channel_name }}」渠道成交单数;与渠道业绩同源:区间内 <b>非取消业务订单</b> 条数,按订单
|
「{{ tb.channel_name }}」渠道成交单数;与渠道业绩同源:区间内 <b>计业绩的业务订单</b> 条数(剔除 4/9/10),按订单
|
||||||
<b>create_time</b> 归日;渠道判定同业绩列。
|
<b>create_time</b> 归日;渠道判定同业绩列。
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -970,7 +974,7 @@
|
|||||||
>
|
>
|
||||||
<span class="yeji-order-drawer__stat-label">
|
<span class="yeji-order-drawer__stat-label">
|
||||||
业务订单金额合计
|
业务订单金额合计
|
||||||
<span class="yeji-order-drawer__stat-note">(不含履约已取消,与上方业绩口径一致)</span>
|
<span class="yeji-order-drawer__stat-note">(不含履约 4·9·10,与上方业绩口径一致)</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="yeji-order-drawer__stat-value yeji-order-drawer__stat-value--money">
|
<span class="yeji-order-drawer__stat-value yeji-order-drawer__stat-value--money">
|
||||||
¥{{ formatOrderDrawerMoney(orderDrawerTotalAmount) }}
|
¥{{ formatOrderDrawerMoney(orderDrawerTotalAmount) }}
|
||||||
@@ -1232,6 +1236,7 @@
|
|||||||
<el-table-column prop="patient_name" label="患者" min-width="92" show-overflow-tooltip />
|
<el-table-column prop="patient_name" label="患者" min-width="92" show-overflow-tooltip />
|
||||||
<el-table-column prop="patient_phone" label="手机" min-width="116" show-overflow-tooltip />
|
<el-table-column prop="patient_phone" label="手机" min-width="116" show-overflow-tooltip />
|
||||||
<el-table-column prop="doctor_name" label="接诊医生" width="92" show-overflow-tooltip />
|
<el-table-column prop="doctor_name" label="接诊医生" width="92" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="assistant_name" label="医助" width="88" show-overflow-tooltip />
|
||||||
<el-table-column label="诊单ID" width="84" align="right">
|
<el-table-column label="诊单ID" width="84" align="right">
|
||||||
<template #default="{ row }">{{ formatInt(row.diagnosis_id ?? 0) }}</template>
|
<template #default="{ row }">{{ formatInt(row.diagnosis_id ?? 0) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -1314,6 +1319,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
import { Search, RefreshRight, InfoFilled } from '@element-plus/icons-vue'
|
import { Search, RefreshRight, InfoFilled } from '@element-plus/icons-vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import vCharts from 'vue-echarts'
|
import vCharts from 'vue-echarts'
|
||||||
@@ -2075,7 +2081,9 @@ async function loadDoctorDailyStats() {
|
|||||||
if (res?.start_date && res?.end_date) {
|
if (res?.start_date && res?.end_date) {
|
||||||
doctorDailyRange.value = { start: res.start_date, end: res.end_date }
|
doctorDailyRange.value = { start: res.start_date, end: res.end_date }
|
||||||
}
|
}
|
||||||
} catch (_e) {
|
} catch (e: unknown) {
|
||||||
|
// 与业绩主接口并发时,同源请求键相同会触发 axios 重复请求取消;勿清空状态以免覆盖后到的成功结果
|
||||||
|
if (axios.isCancel(e)) return
|
||||||
doctorDailyRows.value = []
|
doctorDailyRows.value = []
|
||||||
doctorDailyTotal.value = {}
|
doctorDailyTotal.value = {}
|
||||||
doctorDailyRange.value = null
|
doctorDailyRange.value = null
|
||||||
@@ -2124,7 +2132,7 @@ type OrderDrawerFilter =
|
|||||||
}
|
}
|
||||||
|
|
||||||
const orderDrawerFilter = ref<OrderDrawerFilter | null>(null)
|
const orderDrawerFilter = ref<OrderDrawerFilter | null>(null)
|
||||||
/** 列表接口 extend,含 stats_order_amount_performance(与当前筛选、不含已取消口径一致) */
|
/** 列表接口 extend,含 stats_order_amount_performance(与当前筛选一致:剔除履约 4/9/10) */
|
||||||
const orderDrawerExtend = ref<Record<string, any> | null>(null)
|
const orderDrawerExtend = ref<Record<string, any> | null>(null)
|
||||||
|
|
||||||
const orderDrawerTotalAmount = computed(() => {
|
const orderDrawerTotalAmount = computed(() => {
|
||||||
@@ -2472,7 +2480,8 @@ async function loadLeaderboard(range: { start: string; end: string }) {
|
|||||||
range_note: res.range_note || '',
|
range_note: res.range_note || '',
|
||||||
leaderboards: res.leaderboards || [],
|
leaderboards: res.leaderboards || [],
|
||||||
}
|
}
|
||||||
} catch (_e) {
|
} catch (e: unknown) {
|
||||||
|
if (axios.isCancel(e)) return
|
||||||
leaderboardBlock.value = null
|
leaderboardBlock.value = null
|
||||||
} finally {
|
} finally {
|
||||||
leaderboardsLoading.value = false
|
leaderboardsLoading.value = false
|
||||||
@@ -2527,8 +2536,10 @@ async function loadData() {
|
|||||||
}))
|
}))
|
||||||
const ymd = formatLocalYmd(new Date())
|
const ymd = formatLocalYmd(new Date())
|
||||||
await loadLeaderboard({ start: ymd, end: ymd })
|
await loadLeaderboard({ start: ymd, end: ymd })
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
ElMessage.error(e?.msg || e?.message || '加载失败')
|
if (axios.isCancel(e)) return
|
||||||
|
const any = e as any
|
||||||
|
ElMessage.error(any?.msg || any?.message || '加载失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
if (canViewDoctorDailyStats.value) {
|
if (canViewDoctorDailyStats.value) {
|
||||||
@@ -2636,18 +2647,17 @@ function buildOrderDrawerListParams(): Record<string, any> | null {
|
|||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
start_time: `${f.start} 00:00:00`,
|
start_time: `${f.start} 00:00:00`,
|
||||||
end_time: `${f.end} 23:59:59`,
|
end_time: `${f.end} 23:59:59`,
|
||||||
/** 与业绩列口径一致:不列出履约已取消(4) */
|
/** 与业绩列口径一致:不列出履约 4(已取消)/9(拒收)/10(退款) */
|
||||||
exclude_fulfillment_cancelled: 1,
|
exclude_fulfillment_cancelled: 1,
|
||||||
/** 侧栏展示约诊数等 extend 字段 */
|
/**
|
||||||
|
* 业绩看板入口标志:后端 PrescriptionOrderLists::applyDoctorAssistantFilters 看到此标志后,
|
||||||
|
* assistant_id / assistant_dept_id 一律按订单创建人收窄(与表格诊金/合计业绩/复诊同口径),
|
||||||
|
* 而非默认的「创建人 ∪ 诊单医助」并集。同时启用 extend 字段(约诊数、合计业绩对照)。
|
||||||
|
*/
|
||||||
yeji_order_drawer: 1,
|
yeji_order_drawer: 1,
|
||||||
}
|
}
|
||||||
if (f.mode === 'dept') {
|
if (f.mode === 'dept') {
|
||||||
params.assistant_dept_id = f.deptId
|
params.assistant_dept_id = f.deptId
|
||||||
/** 与 YejiStatsLogic 最深展示行 + 创建人优先归属一致 */
|
|
||||||
if (f.yejiTableRowDeptIds && f.yejiTableRowDeptIds.length > 0) {
|
|
||||||
params.yeji_table_row_dept_ids = f.yejiTableRowDeptIds.join(',')
|
|
||||||
}
|
|
||||||
/** 勿传 yeji_drawer_match_table_performance:该参数按「业绩归因」医助收窄,与看板列表摊行不一致 */
|
|
||||||
if (selectedDeptIds.value.length > 0) {
|
if (selectedDeptIds.value.length > 0) {
|
||||||
params.dept_ids = selectedDeptIds.value.join(',')
|
params.dept_ids = selectedDeptIds.value.join(',')
|
||||||
}
|
}
|
||||||
@@ -2733,8 +2743,10 @@ async function fetchOrderDrawerPage() {
|
|||||||
) {
|
) {
|
||||||
await hydrateOrderDrawerSplitPerfCache()
|
await hydrateOrderDrawerSplitPerfCache()
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
ElMessage.error(e?.msg || e?.message || '加载业务订单失败')
|
if (axios.isCancel(e)) return
|
||||||
|
const any = e as any
|
||||||
|
ElMessage.error(any?.msg || any?.message || '加载业务订单失败')
|
||||||
orderDrawerLists.value = []
|
orderDrawerLists.value = []
|
||||||
orderDrawerCount.value = 0
|
orderDrawerCount.value = 0
|
||||||
orderDrawerExtend.value = null
|
orderDrawerExtend.value = null
|
||||||
@@ -2756,7 +2768,7 @@ function openOrderDrawerByDept(tb: YejiTable, row: YejiRow, opts?: { listTab?: '
|
|||||||
yejiTableRowDeptIds: tb.rows.map(r => r.dept_id).filter((id): id is number => typeof id === 'number' && id > 0),
|
yejiTableRowDeptIds: tb.rows.map(r => r.dept_id).filter((id): id is number => typeof id === 'number' && id > 0),
|
||||||
}
|
}
|
||||||
orderDrawerTitle.value = `业务订单 · ${row.dept_name}`
|
orderDrawerTitle.value = `业务订单 · ${row.dept_name}`
|
||||||
orderDrawerHint.value = `创建时间:${tb.start_date} ~ ${tb.end_date} · 与「接诊诊单」同口径:**创建人**人事部门优先,无创建人则诊单医助;同表多行间取最深归属 · 不含履约已取消`
|
orderDrawerHint.value = `创建时间:${tb.start_date} ~ ${tb.end_date} · 与「合计业绩 / 接诊诊单」同口径:订单创建人人事部门 ∈ 本部门子树 · 不含履约 4/9/10`
|
||||||
orderDrawerPage.value = 1
|
orderDrawerPage.value = 1
|
||||||
orderDrawerVisible.value = true
|
orderDrawerVisible.value = true
|
||||||
void fetchOrderDrawerPage()
|
void fetchOrderDrawerPage()
|
||||||
@@ -2780,7 +2792,7 @@ function openOrderDrawerByYejiTableScope(
|
|||||||
yejiTableRowDeptIds: tb.rows.map(r => r.dept_id).filter((id): id is number => typeof id === 'number' && id > 0),
|
yejiTableRowDeptIds: tb.rows.map(r => r.dept_id).filter((id): id is number => typeof id === 'number' && id > 0),
|
||||||
}
|
}
|
||||||
orderDrawerTitle.value = '业务订单 · 当前展示合计'
|
orderDrawerTitle.value = '业务订单 · 当前展示合计'
|
||||||
orderDrawerHint.value = `创建时间:${tb.start_date} ~ ${tb.end_date} · 已选展示部门 1 个根节点(含其下全部展示行)· 不含履约已取消`
|
orderDrawerHint.value = `创建时间:${tb.start_date} ~ ${tb.end_date} · 已选展示部门 1 个根节点(含其下全部展示行)· 不含履约 4/9/10`
|
||||||
orderDrawerPage.value = 1
|
orderDrawerPage.value = 1
|
||||||
orderDrawerVisible.value = true
|
orderDrawerVisible.value = true
|
||||||
void fetchOrderDrawerPage()
|
void fetchOrderDrawerPage()
|
||||||
@@ -2837,7 +2849,7 @@ function openOrderDrawerByAssistant(row: LeaderboardPack['leaderboards'][0]['row
|
|||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
orderDrawerTitle.value = `业务订单 · ${row.name}`
|
orderDrawerTitle.value = `业务订单 · ${row.name}`
|
||||||
orderDrawerHint.value = `创建时间:${start} ~ ${end} · 诊单医助 · 不含履约已取消`
|
orderDrawerHint.value = `创建时间:${start} ~ ${end} · 创建人=${row.name}(与排行榜诊金同口径)· 不含履约 4/9/10`
|
||||||
orderDrawerPage.value = 1
|
orderDrawerPage.value = 1
|
||||||
orderDrawerVisible.value = true
|
orderDrawerVisible.value = true
|
||||||
void fetchOrderDrawerPage()
|
void fetchOrderDrawerPage()
|
||||||
@@ -2861,7 +2873,7 @@ function openOrderDrawerByAssistantErCenterRevisit(
|
|||||||
}
|
}
|
||||||
const slotLbl = revisitSlot === 0 ? '复诊合计' : yejiRevisitSlotColumnTitle(revisitSlot)
|
const slotLbl = revisitSlot === 0 ? '复诊合计' : yejiRevisitSlotColumnTitle(revisitSlot)
|
||||||
orderDrawerTitle.value = `业务订单 · ${assistant.name} · ${slotLbl}`
|
orderDrawerTitle.value = `业务订单 · ${assistant.name} · ${slotLbl}`
|
||||||
orderDrawerHint.value = `创建时间:${start} ~ ${end} · 诊单医助 · 仅二中心复诊口径 · ${slotLbl} · 不含履约已取消`
|
orderDrawerHint.value = `创建时间:${start} ~ ${end} · 创建人=${assistant.name}(与部门表 / 排行榜复诊同口径)· 仅二中心复诊口径 · ${slotLbl} · 不含履约 4/9/10`
|
||||||
orderDrawerPage.value = 1
|
orderDrawerPage.value = 1
|
||||||
orderDrawerVisible.value = true
|
orderDrawerVisible.value = true
|
||||||
void fetchOrderDrawerPage()
|
void fetchOrderDrawerPage()
|
||||||
@@ -2974,8 +2986,10 @@ async function openYejiRevisitDeptBreakdown(tb: YejiTable, row: YejiRow, revisit
|
|||||||
const res: any = await yejiStatsRevisitBreakdown(p as any)
|
const res: any = await yejiStatsRevisitBreakdown(p as any)
|
||||||
revisitBreakdownRows.value = Array.isArray(res?.rows) ? res.rows : []
|
revisitBreakdownRows.value = Array.isArray(res?.rows) ? res.rows : []
|
||||||
revisitBreakdownNote.value = typeof res?.note === 'string' ? res.note : ''
|
revisitBreakdownNote.value = typeof res?.note === 'string' ? res.note : ''
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
ElMessage.error(e?.msg || e?.message || '加载复诊拆解失败')
|
if (axios.isCancel(e)) return
|
||||||
|
const any = e as any
|
||||||
|
ElMessage.error(any?.msg || any?.message || '加载复诊拆解失败')
|
||||||
revisitBreakdownRows.value = []
|
revisitBreakdownRows.value = []
|
||||||
revisitBreakdownNote.value = ''
|
revisitBreakdownNote.value = ''
|
||||||
} finally {
|
} finally {
|
||||||
@@ -3014,8 +3028,10 @@ async function openUnassignedBreakdown(tb: YejiTable) {
|
|||||||
const res: any = await yejiStatsUnassignedBreakdown(p as any)
|
const res: any = await yejiStatsUnassignedBreakdown(p as any)
|
||||||
unassignedDialogRows.value = Array.isArray(res?.rows) ? res.rows : []
|
unassignedDialogRows.value = Array.isArray(res?.rows) ? res.rows : []
|
||||||
unassignedDialogNote.value = typeof res?.note === 'string' ? res.note : ''
|
unassignedDialogNote.value = typeof res?.note === 'string' ? res.note : ''
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
ElMessage.error(e?.msg || e?.message || '加载未归属拆解失败')
|
if (axios.isCancel(e)) return
|
||||||
|
const any = e as any
|
||||||
|
ElMessage.error(any?.msg || any?.message || '加载未归属拆解失败')
|
||||||
unassignedDialogRows.value = []
|
unassignedDialogRows.value = []
|
||||||
unassignedDialogNote.value = ''
|
unassignedDialogNote.value = ''
|
||||||
} finally {
|
} finally {
|
||||||
@@ -3056,8 +3072,10 @@ async function fetchLeadLinesPage() {
|
|||||||
leadLinesRows.value = Array.isArray(res?.lists) ? res.lists : []
|
leadLinesRows.value = Array.isArray(res?.lists) ? res.lists : []
|
||||||
leadLinesCount.value = Number(res?.count ?? 0)
|
leadLinesCount.value = Number(res?.count ?? 0)
|
||||||
leadLinesApiNote.value = typeof res?.note === 'string' ? res.note : ''
|
leadLinesApiNote.value = typeof res?.note === 'string' ? res.note : ''
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
ElMessage.error(e?.msg || e?.message || '加载进线明细失败')
|
if (axios.isCancel(e)) return
|
||||||
|
const any = e as any
|
||||||
|
ElMessage.error(any?.msg || any?.message || '加载进线明细失败')
|
||||||
leadLinesRows.value = []
|
leadLinesRows.value = []
|
||||||
leadLinesCount.value = 0
|
leadLinesCount.value = 0
|
||||||
leadLinesApiNote.value = ''
|
leadLinesApiNote.value = ''
|
||||||
@@ -3129,8 +3147,10 @@ async function fetchAppointmentLinesPage() {
|
|||||||
appointmentLinesRows.value = Array.isArray(res?.lists) ? res.lists : []
|
appointmentLinesRows.value = Array.isArray(res?.lists) ? res.lists : []
|
||||||
appointmentLinesCount.value = Number(res?.count ?? 0)
|
appointmentLinesCount.value = Number(res?.count ?? 0)
|
||||||
appointmentLinesApiNote.value = typeof res?.note === 'string' ? res.note : ''
|
appointmentLinesApiNote.value = typeof res?.note === 'string' ? res.note : ''
|
||||||
} catch (e: any) {
|
} catch (e: unknown) {
|
||||||
ElMessage.error(e?.msg || e?.message || '加载挂号明细失败')
|
if (axios.isCancel(e)) return
|
||||||
|
const any = e as any
|
||||||
|
ElMessage.error(any?.msg || any?.message || '加载挂号明细失败')
|
||||||
appointmentLinesRows.value = []
|
appointmentLinesRows.value = []
|
||||||
appointmentLinesCount.value = 0
|
appointmentLinesCount.value = 0
|
||||||
appointmentLinesApiNote.value = ''
|
appointmentLinesApiNote.value = ''
|
||||||
|
|||||||
@@ -22,16 +22,25 @@
|
|||||||
<el-select
|
<el-select
|
||||||
v-model="formData.media_channel_code"
|
v-model="formData.media_channel_code"
|
||||||
placeholder="请选择自媒体渠道"
|
placeholder="请选择自媒体渠道"
|
||||||
class="w-full"
|
class="account-cost-channel-select w-full"
|
||||||
filterable
|
filterable
|
||||||
:disabled="mode === 'edit'"
|
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option-group
|
||||||
v-for="item in mediaChannelOptions"
|
v-for="g in mediaChannelGroups"
|
||||||
:key="item.code"
|
:key="g.group_name"
|
||||||
:label="item.name"
|
:label="g.group_name"
|
||||||
:value="item.code"
|
>
|
||||||
/>
|
<el-option
|
||||||
|
v-for="ch in g.channels"
|
||||||
|
:key="ch.channel_code"
|
||||||
|
:label="ch.channel_name"
|
||||||
|
:value="ch.channel_code"
|
||||||
|
>
|
||||||
|
<div class="channel-opt-row">
|
||||||
|
<span class="opt-name">{{ ch.channel_name }}</span>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-option-group>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="部门" prop="dept_id">
|
<el-form-item label="部门" prop="dept_id">
|
||||||
@@ -78,9 +87,9 @@ import type { FormInstance } from 'element-plus'
|
|||||||
import { accountCostAdd, accountCostDetail, accountCostEdit } from '@/api/finance'
|
import { accountCostAdd, accountCostDetail, accountCostEdit } from '@/api/finance'
|
||||||
import Popup from '@/components/popup/index.vue'
|
import Popup from '@/components/popup/index.vue'
|
||||||
|
|
||||||
interface MediaChannelOption {
|
interface MediaChannelGroup {
|
||||||
code: string
|
group_name: string
|
||||||
name: string
|
channels: { channel_code: string; channel_name: string }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeptOption {
|
interface DeptOption {
|
||||||
@@ -90,7 +99,7 @@ interface DeptOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
mediaChannelOptions: MediaChannelOption[]
|
mediaChannelGroups: MediaChannelGroup[]
|
||||||
deptOptions: DeptOption[]
|
deptOptions: DeptOption[]
|
||||||
defaultMediaChannelCode: string
|
defaultMediaChannelCode: string
|
||||||
}>()
|
}>()
|
||||||
@@ -197,3 +206,21 @@ defineExpose({
|
|||||||
getDetail,
|
getDetail,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.account-cost-channel-select {
|
||||||
|
:deep(.channel-opt-row) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
:deep(.opt-name) {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -27,14 +27,25 @@
|
|||||||
placeholder="筛选全部渠道"
|
placeholder="筛选全部渠道"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
class="w-[220px]"
|
class="account-cost-channel-select w-[220px]"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option label="全部" value="" />
|
||||||
v-for="item in mediaChannelOptions"
|
<el-option-group
|
||||||
:key="item.code"
|
v-for="g in mediaChannelGroups"
|
||||||
:label="item.name"
|
:key="g.group_name"
|
||||||
:value="item.code"
|
:label="g.group_name"
|
||||||
/>
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="ch in g.channels"
|
||||||
|
:key="ch.channel_code"
|
||||||
|
:label="ch.channel_name"
|
||||||
|
:value="ch.channel_code"
|
||||||
|
>
|
||||||
|
<div class="channel-opt-row">
|
||||||
|
<span class="opt-name">{{ ch.channel_name }}</span>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-option-group>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="部门">
|
<el-form-item label="部门">
|
||||||
@@ -78,7 +89,15 @@
|
|||||||
|
|
||||||
<el-table class="mt-4" size="large" v-loading="pager.loading" :data="pager.lists">
|
<el-table class="mt-4" size="large" v-loading="pager.loading" :data="pager.lists">
|
||||||
<el-table-column label="日期" prop="cost_date" min-width="120" />
|
<el-table-column label="日期" prop="cost_date" min-width="120" />
|
||||||
<el-table-column label="自媒体渠道" prop="media_channel_name" min-width="120" />
|
<el-table-column label="自媒体渠道" min-width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="getChannelGroupLabel(row.media_channel_code)">
|
||||||
|
<el-tag size="small" class="mr-1">{{ getChannelGroupLabel(row.media_channel_code) }}</el-tag>
|
||||||
|
{{ row.media_channel_name }}
|
||||||
|
</span>
|
||||||
|
<span v-else>{{ row.media_channel_name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="部门" prop="dept_name" min-width="140" />
|
<el-table-column label="部门" prop="dept_name" min-width="140" />
|
||||||
<el-table-column label="账户消耗" min-width="120">
|
<el-table-column label="账户消耗" min-width="120">
|
||||||
<template #default="{ row }">¥{{ row.amount }}</template>
|
<template #default="{ row }">¥{{ row.amount }}</template>
|
||||||
@@ -117,7 +136,7 @@
|
|||||||
<edit-popup
|
<edit-popup
|
||||||
v-if="showEdit"
|
v-if="showEdit"
|
||||||
ref="editRef"
|
ref="editRef"
|
||||||
:media-channel-options="mediaChannelOptions"
|
:media-channel-groups="mediaChannelGroups"
|
||||||
:dept-options="deptOptions"
|
:dept-options="deptOptions"
|
||||||
:default-media-channel-code="defaultMediaChannelCode"
|
:default-media-channel-code="defaultMediaChannelCode"
|
||||||
@success="getLists"
|
@success="getLists"
|
||||||
@@ -133,6 +152,11 @@ import feedback from '@/utils/feedback'
|
|||||||
|
|
||||||
import EditPopup from './edit.vue'
|
import EditPopup from './edit.vue'
|
||||||
|
|
||||||
|
interface MediaChannelGroup {
|
||||||
|
group_name: string
|
||||||
|
channels: { channel_code: string; channel_name: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
interface MediaChannelOption {
|
interface MediaChannelOption {
|
||||||
code: string
|
code: string
|
||||||
name: string
|
name: string
|
||||||
@@ -160,7 +184,7 @@ const { pager, getLists, resetPage } = usePaging({
|
|||||||
params: queryParams,
|
params: queryParams,
|
||||||
})
|
})
|
||||||
|
|
||||||
const mediaChannelOptions = computed<MediaChannelOption[]>(() => {
|
const mediaChannelOptionsFlat = computed<MediaChannelOption[]>(() => {
|
||||||
const options = pager.extend.media_channel_options
|
const options = pager.extend.media_channel_options
|
||||||
if (!Array.isArray(options)) return []
|
if (!Array.isArray(options)) return []
|
||||||
|
|
||||||
@@ -170,6 +194,26 @@ const mediaChannelOptions = computed<MediaChannelOption[]>(() => {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const mediaChannelGroups = computed<MediaChannelGroup[]>(() => {
|
||||||
|
const groups = pager.extend.media_channel_groups
|
||||||
|
if (Array.isArray(groups) && groups.length > 0) {
|
||||||
|
return groups as MediaChannelGroup[]
|
||||||
|
}
|
||||||
|
const flat = mediaChannelOptionsFlat.value
|
||||||
|
if (!flat.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
group_name: '渠道',
|
||||||
|
channels: flat.map(item => ({
|
||||||
|
channel_code: item.code,
|
||||||
|
channel_name: item.name,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
const deptOptions = computed<DeptOption[]>(() => {
|
const deptOptions = computed<DeptOption[]>(() => {
|
||||||
const options = pager.extend.dept_options
|
const options = pager.extend.dept_options
|
||||||
return Array.isArray(options) ? options : []
|
return Array.isArray(options) ? options : []
|
||||||
@@ -177,6 +221,17 @@ const deptOptions = computed<DeptOption[]>(() => {
|
|||||||
|
|
||||||
const defaultMediaChannelCode = computed(() => String(pager.extend.default_media_channel_code || ''))
|
const defaultMediaChannelCode = computed(() => String(pager.extend.default_media_channel_code || ''))
|
||||||
|
|
||||||
|
const getChannelGroupLabel = (channelCode: string): string => {
|
||||||
|
if (!channelCode) return ''
|
||||||
|
for (const group of mediaChannelGroups.value) {
|
||||||
|
const channel = group.channels.find(ch => ch.channel_code === channelCode)
|
||||||
|
if (channel) {
|
||||||
|
return group.group_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
const treeProps = {
|
const treeProps = {
|
||||||
value: 'id',
|
value: 'id',
|
||||||
label: 'name',
|
label: 'name',
|
||||||
@@ -213,3 +268,21 @@ const handleReset = () => {
|
|||||||
|
|
||||||
getLists()
|
getLists()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.account-cost-channel-select {
|
||||||
|
:deep(.channel-opt-row) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
:deep(.opt-name) {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,273 @@
|
|||||||
|
<template>
|
||||||
|
<div class="assistant-performance-page">
|
||||||
|
<el-card class="!border-none" shadow="never">
|
||||||
|
<el-form :inline="true" class="stats-filter-form">
|
||||||
|
<el-form-item label="时间范围">
|
||||||
|
<el-radio-group v-model="queryParams.time_type" @change="handleTimeTypeChange">
|
||||||
|
<el-radio-button label="today">今天</el-radio-button>
|
||||||
|
<el-radio-button label="yesterday">昨天</el-radio-button>
|
||||||
|
<el-radio-button label="week">最近7天</el-radio-button>
|
||||||
|
<el-radio-button label="month">最近30天</el-radio-button>
|
||||||
|
<el-radio-button label="custom">自定义</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item v-if="queryParams.time_type === 'custom'">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
style="width: 260px"
|
||||||
|
@change="handleCustomDateChange"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :loading="loading" @click="fetchData">查询</el-button>
|
||||||
|
<el-button @click="handleReset">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<div class="stats-kpi-grid">
|
||||||
|
<div v-for="card in summaryCards" :key="card.key" class="stats-kpi-card" :class="card.cardClass">
|
||||||
|
<div class="stats-kpi-label">{{ card.label }}</div>
|
||||||
|
<div class="stats-kpi-value" :class="{ 'is-money': card.type === 'money' }">
|
||||||
|
{{ formatValue(card.key, card.type) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-card class="!border-none mt-4" shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">业绩趋势</span>
|
||||||
|
<span class="card-hint">{{ dateRangeText }} · 履约完成业绩</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<v-charts
|
||||||
|
v-if="chartHasData"
|
||||||
|
class="stats-chart"
|
||||||
|
:option="chartOption"
|
||||||
|
autoresize
|
||||||
|
/>
|
||||||
|
<el-empty v-else description="暂无数据" />
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="assistantPerformancePage">
|
||||||
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import vCharts from 'vue-echarts'
|
||||||
|
import { assistantPerformanceOverview } from '@/api/stats'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const dateRange = ref<string[]>([])
|
||||||
|
const queryParams = reactive({
|
||||||
|
time_type: 'month',
|
||||||
|
start_date: '',
|
||||||
|
end_date: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const overview = reactive<Record<string, any>>({
|
||||||
|
date_range: [],
|
||||||
|
summary: {
|
||||||
|
total_amount: 0,
|
||||||
|
total_count: 0
|
||||||
|
},
|
||||||
|
chart: {
|
||||||
|
dates: [],
|
||||||
|
amounts: [],
|
||||||
|
counts: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const summaryCards = [
|
||||||
|
{ key: 'total_amount', label: '业绩', type: 'money', cardClass: '' },
|
||||||
|
{ key: 'total_count', label: '有效订单数', type: 'count', cardClass: '' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const dateRangeText = computed(() => {
|
||||||
|
if (!overview.date_range?.length) return '未选择'
|
||||||
|
return `${overview.date_range[0]} 至 ${overview.date_range[1]}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const chartHasData = computed(() => {
|
||||||
|
return overview.chart.dates.length > 0 && overview.chart.amounts.some((v: number) => v > 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const chartOption = computed(() => ({
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: (params: any) => {
|
||||||
|
const p = params[0]
|
||||||
|
return `${p.axisValue}<br/>${p.marker}${p.seriesName}: ¥${Number(p.value).toFixed(2)}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: { left: 60, right: 24, top: 36, bottom: 36 },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: overview.chart.dates,
|
||||||
|
axisLabel: {
|
||||||
|
interval: overview.chart.dates.length > 15 ? 'auto' : 0,
|
||||||
|
rotate: overview.chart.dates.length > 10 ? 30 : 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: '金额(元)',
|
||||||
|
axisLabel: {
|
||||||
|
formatter: (val: number) => val >= 10000 ? (val / 10000).toFixed(1) + '万' : String(val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '业绩',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
showSymbol: true,
|
||||||
|
symbolSize: 6,
|
||||||
|
lineStyle: { width: 3, color: '#4a78ff' },
|
||||||
|
itemStyle: { color: '#4a78ff' },
|
||||||
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear',
|
||||||
|
x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(74, 120, 255, 0.25)' },
|
||||||
|
{ offset: 1, color: 'rgba(74, 120, 255, 0.02)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: overview.chart.amounts
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
|
||||||
|
const formatValue = (key: string, type: string) => {
|
||||||
|
const value = overview.summary?.[key] ?? 0
|
||||||
|
if (type === 'money') return `¥${Number(value).toFixed(2)}`
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params: Record<string, any> = { time_type: queryParams.time_type }
|
||||||
|
if (queryParams.time_type === 'custom') {
|
||||||
|
params.start_date = dateRange.value[0] || ''
|
||||||
|
params.end_date = dateRange.value[1] || ''
|
||||||
|
}
|
||||||
|
const res = await assistantPerformanceOverview(params)
|
||||||
|
Object.assign(overview, res || {})
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('获取业绩数据失败:', error)
|
||||||
|
ElMessage.error(error?.msg || '获取业绩数据失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTimeTypeChange = () => {
|
||||||
|
if (queryParams.time_type !== 'custom') {
|
||||||
|
dateRange.value = []
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCustomDateChange = () => {
|
||||||
|
if (dateRange.value?.length === 2) {
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
queryParams.time_type = 'month'
|
||||||
|
dateRange.value = []
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.assistant-performance-page {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-filter-form {
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-kpi-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-kpi-card {
|
||||||
|
background: linear-gradient(145deg, #ffffff, #f5f8ff);
|
||||||
|
border: 1px solid #ebf1ff;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 10px 24px rgba(74, 120, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-kpi-card.is-cancelled {
|
||||||
|
background: linear-gradient(145deg, #ffffff, #fff5f5);
|
||||||
|
border-color: #ffe0e0;
|
||||||
|
box-shadow: 0 10px 24px rgba(239, 68, 68, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-kpi-label {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-kpi-value {
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-kpi-value.is-money {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-kpi-card.is-cancelled .stats-kpi-value {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-hint {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-chart {
|
||||||
|
height: 360px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in channelOptions"
|
v-for="item in channelOptions"
|
||||||
:key="item.value"
|
:key="String(item.value)"
|
||||||
:label="item.name"
|
:label="item.name"
|
||||||
:value="item.value"
|
:value="item.value"
|
||||||
/>
|
/>
|
||||||
@@ -470,7 +470,11 @@ const loadChannelOptions = async () => {
|
|||||||
if (ds !== 0) return ds
|
if (ds !== 0) return ds
|
||||||
return Number(b?.id ?? 0) - Number(a?.id ?? 0)
|
return Number(b?.id ?? 0) - Number(a?.id ?? 0)
|
||||||
})
|
})
|
||||||
channelOptions.value = rows
|
// 统一为字符串,避免字典 value 为数字时 el-select 与表单校验不一致导致看似选了但实际未绑定
|
||||||
|
channelOptions.value = rows.map((row: any) => ({
|
||||||
|
...row,
|
||||||
|
value: row?.value != null && row.value !== '' ? String(row.value) : ''
|
||||||
|
}))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载渠道来源失败:', e)
|
console.error('加载渠道来源失败:', e)
|
||||||
channelOptions.value = []
|
channelOptions.value = []
|
||||||
@@ -693,7 +697,7 @@ const handleConfirm = async () => {
|
|||||||
appointment_time: form.appointmentTime,
|
appointment_time: form.appointmentTime,
|
||||||
appointment_type: form.appointmentType,
|
appointment_type: form.appointmentType,
|
||||||
remark: form.remark,
|
remark: form.remark,
|
||||||
channel_source: form.channel_source,
|
channel_source: String(form.channel_source ?? ''),
|
||||||
channel_source_detail: form.channel_source_detail.trim()
|
channel_source_detail: form.channel_source_detail.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,8 @@ const load = async () => {
|
|||||||
await loadChannels()
|
await loadChannels()
|
||||||
const res = await appointmentLists({
|
const res = await appointmentLists({
|
||||||
patient_id: props.diagnosisId,
|
patient_id: props.diagnosisId,
|
||||||
|
/** 诊单维度拉挂号:后端豁免医生/医助与数据范围收窄 */
|
||||||
|
diag_scope_relax: 1,
|
||||||
page_no: 1,
|
page_no: 1,
|
||||||
page_size: 500
|
page_size: 500
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -683,7 +683,7 @@ const bloodTrendOption = computed(() => ({
|
|||||||
name: '空腹血糖',
|
name: '空腹血糖',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
connectNulls: false,
|
connectNulls: true,
|
||||||
data: bloodTrendSeries.value.fasting,
|
data: bloodTrendSeries.value.fasting,
|
||||||
symbolSize: 7,
|
symbolSize: 7,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
@@ -698,7 +698,7 @@ const bloodTrendOption = computed(() => ({
|
|||||||
name: '餐后血糖',
|
name: '餐后血糖',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
connectNulls: false,
|
connectNulls: true,
|
||||||
data: bloodTrendSeries.value.postprandial,
|
data: bloodTrendSeries.value.postprandial,
|
||||||
symbolSize: 7,
|
symbolSize: 7,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
@@ -719,7 +719,8 @@ function formatColumnDate(date: string) {
|
|||||||
function parseTrendNumber(value: unknown): number | null {
|
function parseTrendNumber(value: unknown): number | null {
|
||||||
if (value === '' || value == null) return null
|
if (value === '' || value == null) return null
|
||||||
const parsed = Number(value)
|
const parsed = Number(value)
|
||||||
return Number.isFinite(parsed) ? parsed : null
|
if (!Number.isFinite(parsed) || parsed === 0) return null
|
||||||
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCell(metric: string, date: string): { value: string; isHigh: boolean; hasRecord: boolean } {
|
function getCell(metric: string, date: string): { value: string; isHigh: boolean; hasRecord: boolean } {
|
||||||
|
|||||||
@@ -131,7 +131,7 @@
|
|||||||
<el-descriptions-item label="医师">{{ detailPrescription.doctor_name || '—' }}</el-descriptions-item>
|
<el-descriptions-item label="医师">{{ detailPrescription.doctor_name || '—' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="类型">{{ detailPrescription.prescription_type || '—' }}</el-descriptions-item>
|
<el-descriptions-item label="类型">{{ detailPrescription.prescription_type || '—' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="临床诊断" :span="2">{{ detailPrescription.clinical_diagnosis || '—' }}</el-descriptions-item>
|
<el-descriptions-item label="临床诊断" :span="2">{{ detailPrescription.clinical_diagnosis || '—' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="剂数 / 用法" :span="2">
|
<el-descriptions-item label="剂数 / 用法" :span="2" v-if="showPrescriptionAuditFilter">
|
||||||
{{ detailData.dose_count ?? detailPrescription.dose_count ?? '—' }} {{ detailData.dose_unit || '剂' }} ·
|
{{ detailData.dose_count ?? detailPrescription.dose_count ?? '—' }} {{ detailData.dose_unit || '剂' }} ·
|
||||||
{{ detailPrescription.usage_instruction || detailPrescription.usage_method || '—' }}
|
{{ detailPrescription.usage_instruction || detailPrescription.usage_method || '—' }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
@@ -274,6 +274,7 @@
|
|||||||
<el-descriptions-item label="用药疗程">{{ detailData.medication_days ? detailData.medication_days + ' 天' : '—' }}</el-descriptions-item>
|
<el-descriptions-item label="用药疗程">{{ detailData.medication_days ? detailData.medication_days + ' 天' : '—' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="上次医护">{{ detailData.prev_staff || '—' }}</el-descriptions-item>
|
<el-descriptions-item label="上次医护">{{ detailData.prev_staff || '—' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="服务渠道">{{ detailData.service_channel || '—' }}</el-descriptions-item>
|
<el-descriptions-item label="服务渠道">{{ detailData.service_channel || '—' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="服务套餐">{{ formatServicePackage(detailData.service_package) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="费用类别">{{ feeTypeText(detailData.fee_type) }}</el-descriptions-item>
|
<el-descriptions-item label="费用类别">{{ feeTypeText(detailData.fee_type) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="快递单号">{{ detailData.tracking_number || '—' }}</el-descriptions-item>
|
<el-descriptions-item label="快递单号">{{ detailData.tracking_number || '—' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="快递公司">{{ expressCompanyLabel(detailData.express_company) }}</el-descriptions-item>
|
<el-descriptions-item label="快递公司">{{ expressCompanyLabel(detailData.express_company) }}</el-descriptions-item>
|
||||||
@@ -332,7 +333,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, watch, ref } from 'vue'
|
import { computed, watch, ref, onMounted } from 'vue'
|
||||||
import { usePaging } from '@/hooks/usePaging'
|
import { usePaging } from '@/hooks/usePaging'
|
||||||
import {
|
import {
|
||||||
prescriptionOrderLists,
|
prescriptionOrderLists,
|
||||||
@@ -341,6 +342,7 @@ import {
|
|||||||
prescriptionOrderLogisticsTrace,
|
prescriptionOrderLogisticsTrace,
|
||||||
prescriptionOrderPaidPayOrders
|
prescriptionOrderPaidPayOrders
|
||||||
} from '@/api/tcm'
|
} from '@/api/tcm'
|
||||||
|
import { getDictData } from '@/api/app'
|
||||||
import { hasPermission } from '@/utils/perm'
|
import { hasPermission } from '@/utils/perm'
|
||||||
import feedback from '@/utils/feedback'
|
import feedback from '@/utils/feedback'
|
||||||
import useUserStore from '@/stores/modules/user'
|
import useUserStore from '@/stores/modules/user'
|
||||||
@@ -377,6 +379,22 @@ const buildParams = () => {
|
|||||||
queryParams.scene = 'diagnosis_edit'
|
queryParams.scene = 'diagnosis_edit'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 诊间医助角色(与 DiagnosisLists 等一致) */
|
||||||
|
const TCM_ASSISTANT_ROLE_ID = 2
|
||||||
|
/** 与 server/config/project.php prescription_audit_roles 默认一致,可处方审核的角色 */
|
||||||
|
const PRESCRIPTION_AUDIT_ROLE_IDS = [0, 3, 6]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否展示「处方审核」相关字段
|
||||||
|
*/
|
||||||
|
const showPrescriptionAuditFilter = computed(() => {
|
||||||
|
const u = userStore.userInfo
|
||||||
|
if (!u || Number(u.root) === 1) return true
|
||||||
|
const ids = Array.isArray(u.role_ids) ? u.role_ids.map((n: unknown) => Number(n)) : []
|
||||||
|
if (!ids.includes(TCM_ASSISTANT_ROLE_ID)) return true
|
||||||
|
return ids.some((id) => PRESCRIPTION_AUDIT_ROLE_IDS.includes(id))
|
||||||
|
})
|
||||||
|
|
||||||
// ─── 详情抽屉 ───
|
// ─── 详情抽屉 ───
|
||||||
const detailVisible = ref(false)
|
const detailVisible = ref(false)
|
||||||
const detailLoading = ref(false)
|
const detailLoading = ref(false)
|
||||||
@@ -563,6 +581,39 @@ function feeTypeText(t: number | undefined) {
|
|||||||
return m[Number(t)] ?? '—'
|
return m[Number(t)] ?? '—'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 服务套餐字典选项(与 order_list.vue 数据源一致:dict 类型 server_order)
|
||||||
|
const servicePackageOptions = ref<Array<{ name: string; value: string }>>([])
|
||||||
|
|
||||||
|
async function loadServicePackageOptions() {
|
||||||
|
try {
|
||||||
|
const data: any = await getDictData({ type: 'server_order' })
|
||||||
|
const list = (data?.server_order || []) as Array<{ name: string; value: string; status?: number }>
|
||||||
|
servicePackageOptions.value = list.filter((item) => item.status !== 0)
|
||||||
|
} catch {
|
||||||
|
servicePackageOptions.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatServicePackage(value: unknown): string {
|
||||||
|
if (value === null || value === undefined || value === '') return '—'
|
||||||
|
let packages: string[] = []
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
packages = value.map((v) => String(v)).filter((v) => v !== '')
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
packages = value.split(',').map((v) => v.trim()).filter((v) => v !== '')
|
||||||
|
}
|
||||||
|
if (packages.length === 0) return '—'
|
||||||
|
const names = packages.map((val) => {
|
||||||
|
const opt = servicePackageOptions.value.find((o) => o.value === val)
|
||||||
|
return opt ? opt.name : val
|
||||||
|
})
|
||||||
|
return names.join('、')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadServicePackageOptions()
|
||||||
|
})
|
||||||
|
|
||||||
function expressCompanyLabel(v: unknown) {
|
function expressCompanyLabel(v: unknown) {
|
||||||
const s = String(v || '').toLowerCase()
|
const s = String(v || '').toLowerCase()
|
||||||
if (s === 'sf') return '顺丰速运'
|
if (s === 'sf') return '顺丰速运'
|
||||||
|
|||||||
@@ -257,7 +257,7 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
v-if="row.last_blood_record_at"
|
v-if="row.last_blood_record_at"
|
||||||
:content="`最近一次血糖打卡:${row.last_blood_record_at}`"
|
:content="`最近一次健康打卡:${row.last_blood_record_at}`"
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<span class="unserved-days" :class="unservedDaysClass(row.unserved_days)">
|
<span class="unserved-days" :class="unservedDaysClass(row.unserved_days)">
|
||||||
@@ -797,6 +797,32 @@ function buildTcmDiagnosisListRequestPayload(req: Record<string, unknown>): Reco
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待分配「订单月份」:未选月份时,未进入 Tab 前默认当月(与 handlePendingAssignTabClick 一致);
|
||||||
|
* 在 Tab 内清空月份表示不限月份。
|
||||||
|
*/
|
||||||
|
function resolvePendingAssignOrderMonthForRequest(): string {
|
||||||
|
const trimmed = String(formData.pending_assign_order_month ?? '').trim()
|
||||||
|
if (trimmed !== '') return trimmed
|
||||||
|
if (formData.pending_assign === '1') return ''
|
||||||
|
return dayjs().format('YYYY-MM')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 待分配角标 count 请求:与列表同条件,且去掉其它顶部 Tab 残留(如默认「当天挂号」) */
|
||||||
|
function buildPendingAssignCountPayload(): Record<string, unknown> {
|
||||||
|
return buildTcmDiagnosisListRequestPayload({
|
||||||
|
...formData,
|
||||||
|
page_no: 1,
|
||||||
|
page_size: 1,
|
||||||
|
pending_assign: 1,
|
||||||
|
appointment_date: '',
|
||||||
|
has_appointment: '',
|
||||||
|
pending_booking: '',
|
||||||
|
completed_appointment: '',
|
||||||
|
pending_assign_order_month: resolvePendingAssignOrderMonthForRequest()
|
||||||
|
} as Record<string, unknown>) as Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
const fetchTcmDiagnosisListsForPaging = (req: Record<string, unknown>) =>
|
const fetchTcmDiagnosisListsForPaging = (req: Record<string, unknown>) =>
|
||||||
tcmDiagnosisLists(buildTcmDiagnosisListRequestPayload(req) as any)
|
tcmDiagnosisLists(buildTcmDiagnosisListRequestPayload(req) as any)
|
||||||
|
|
||||||
@@ -925,16 +951,7 @@ const fetchDateCounts = async () => {
|
|||||||
tcmDiagnosisLists({ page_no: 1, page_size: 1 }),
|
tcmDiagnosisLists({ page_no: 1, page_size: 1 }),
|
||||||
tcmDiagnosisLists({ has_appointment: 0, page_no: 1, page_size: 1 }),
|
tcmDiagnosisLists({ has_appointment: 0, page_no: 1, page_size: 1 }),
|
||||||
tcmDiagnosisLists({ completed_appointment: 1, page_no: 1, page_size: 1 }),
|
tcmDiagnosisLists({ completed_appointment: 1, page_no: 1, page_size: 1 }),
|
||||||
tcmDiagnosisLists(
|
tcmDiagnosisLists(buildPendingAssignCountPayload() as any)
|
||||||
buildTcmDiagnosisListRequestPayload({
|
|
||||||
pending_assign: 1,
|
|
||||||
page_no: 1,
|
|
||||||
page_size: 1,
|
|
||||||
pending_assign_order_month: formData.pending_assign_order_month,
|
|
||||||
pending_assign_keyword: formData.pending_assign_keyword,
|
|
||||||
keyword: formData.keyword
|
|
||||||
}) as any
|
|
||||||
)
|
|
||||||
])
|
])
|
||||||
dateCounts.value = {
|
dateCounts.value = {
|
||||||
[yesterdayStr.value]: yesterday?.count ?? 0,
|
[yesterdayStr.value]: yesterday?.count ?? 0,
|
||||||
|
|||||||
@@ -17,14 +17,14 @@
|
|||||||
</span>
|
</span>
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
v-if="lastBloodAt"
|
v-if="lastBloodAt"
|
||||||
:content="`最近一次血糖打卡:${lastBloodAt}`"
|
:content="`最近一次健康打卡:${lastBloodAt}`"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<span :class="['unserved-badge', unservedDaysClass(unservedDays)]">
|
<span :class="['unserved-badge', unservedDaysClass(unservedDays)]">
|
||||||
未服务 {{ unservedDays ?? '—' }} 天
|
未服务 {{ unservedDays ?? '—' }} 天
|
||||||
</span>
|
</span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<span v-else class="unserved-badge unserved-muted">从未打卡血糖</span>
|
<span v-else class="unserved-badge unserved-muted">从未打卡</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
+34
@@ -64,6 +64,40 @@ class AppointmentController extends BaseAdminController
|
|||||||
return $this->dataLists(new AppointmentLists());
|
return $this->dataLists(new AppointmentLists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后台编辑挂号记录(消费者处方-挂号列表等,perms: doctor.appointment/edit)
|
||||||
|
*/
|
||||||
|
public function edit()
|
||||||
|
{
|
||||||
|
$params = (new AppointmentValidate())->post()->goCheck('adminEdit');
|
||||||
|
$post = $this->request->post();
|
||||||
|
if (array_key_exists('assistant_id', $post)) {
|
||||||
|
$params['assistant_id'] = $post['assistant_id'];
|
||||||
|
}
|
||||||
|
$result = AppointmentLogic::adminEdit($params, $this->adminId, $this->adminInfo);
|
||||||
|
if ($result === false) {
|
||||||
|
return $this->fail(AppointmentLogic::getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success('保存成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量修改挂号渠道来源(消费者处方-挂号列表) */
|
||||||
|
public function batchEditChannel()
|
||||||
|
{
|
||||||
|
$params = (new AppointmentValidate())->post()->goCheck('batchEditChannel');
|
||||||
|
$post = $this->request->post();
|
||||||
|
if (\array_key_exists('channel_source_detail', $post)) {
|
||||||
|
$params['channel_source_detail'] = $post['channel_source_detail'];
|
||||||
|
}
|
||||||
|
$result = AppointmentLogic::adminBatchEditChannel($params, $this->adminId, $this->adminInfo);
|
||||||
|
if ($result === false) {
|
||||||
|
return $this->fail(AppointmentLogic::getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success((string) ($result['msg'] ?? '操作成功'), $result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @notes 预约详情
|
* @notes 预约详情
|
||||||
* @return \think\response\Json
|
* @return \think\response\Json
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\adminapi\controller\stats;
|
||||||
|
|
||||||
|
use app\adminapi\controller\BaseAdminController;
|
||||||
|
use app\adminapi\logic\stats\AssistantPerformanceLogic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 医助个人业绩
|
||||||
|
*
|
||||||
|
* - GET stats.assistantPerformance/overview 个人业绩概览
|
||||||
|
*/
|
||||||
|
class AssistantPerformanceController extends BaseAdminController
|
||||||
|
{
|
||||||
|
public function overview()
|
||||||
|
{
|
||||||
|
$params = $this->request->get();
|
||||||
|
return $this->data(AssistantPerformanceLogic::overview($params, $this->adminId, $this->adminInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace app\adminapi\controller\stats;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
use app\adminapi\controller\BaseAdminController;
|
||||||
|
|
||||||
|
use app\adminapi\logic\stats\YejiStatsLogic;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
* 提成结算业绩(独立于业绩看板 yejiStats 接口)
|
||||||
|
|
||||||
|
*
|
||||||
|
|
||||||
|
* - GET stats.commissionSettlement/overview
|
||||||
|
|
||||||
|
* - GET stats.commissionSettlement/orderLines
|
||||||
|
|
||||||
|
* - GET stats.commissionSettlement/confirmStatus
|
||||||
|
|
||||||
|
* - POST stats.commissionSettlement/saveReconcile
|
||||||
|
|
||||||
|
* - POST stats.commissionSettlement/confirmFinalize
|
||||||
|
|
||||||
|
* - POST stats.commissionSettlement/confirmRevoke
|
||||||
|
|
||||||
|
* - GET stats.commissionSettlement/deptOptions
|
||||||
|
|
||||||
|
* - GET stats.commissionSettlement/channelOptions
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
class CommissionSettlementController extends BaseAdminController
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
public function overview()
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
@set_time_limit(120);
|
||||||
|
|
||||||
|
$params = $this->request->get();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return $this->data(YejiStatsLogic::commissionSettlementOverview($params, $this->adminId, $this->adminInfo));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function orderLines()
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
@set_time_limit(120);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return $this->data(YejiStatsLogic::commissionSettlementOrderLines($this->request->get(), $this->adminId, $this->adminInfo));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function confirmStatus()
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
return $this->data(YejiStatsLogic::commissionSettlementConfirmStatus($this->request->get(), $this->adminId, $this->adminInfo));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function saveReconcile()
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
return $this->data(YejiStatsLogic::commissionSettlementSaveReconcile($this->request->post(), $this->adminId, $this->adminInfo));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function confirmFinalize()
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
@set_time_limit(120);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return $this->data(YejiStatsLogic::commissionSettlementConfirmFinalize($this->request->post(), $this->adminId, $this->adminInfo));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function confirmRevoke()
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
return $this->data(YejiStatsLogic::commissionSettlementConfirmRevoke($this->request->post(), $this->adminId, $this->adminInfo));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function deptOptions()
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
return $this->data(YejiStatsLogic::deptOptions($this->adminId, $this->adminInfo));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function channelOptions()
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
return $this->data(YejiStatsLogic::channelOptions(false));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user