Compare commits
51
Commits
yizhu-2026512
...
dd-5-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
752eddb0ba | ||
|
|
1005859404 | ||
|
|
ea6c73c063 | ||
|
|
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 | ||
|
|
fdcfa30810 | ||
|
|
4caa98af3a | ||
|
|
bb87e4fb4c | ||
|
|
039105580c | ||
|
|
f47f431bdf |
@@ -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 })
|
||||||
|
|||||||
+29
-1
@@ -110,6 +110,13 @@ const COMMISSION_SETTLEMENT_TIMEOUT_MS = 120000
|
|||||||
/** 提成结算业绩(独立于业绩看板 yejiStats) */
|
/** 提成结算业绩(独立于业绩看板 yejiStats) */
|
||||||
export function commissionSettlementOverview(params: {
|
export function commissionSettlementOverview(params: {
|
||||||
settlement_month: string
|
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
|
dept_ids?: number[] | string
|
||||||
channel_code?: string
|
channel_code?: string
|
||||||
}) {
|
}) {
|
||||||
@@ -127,14 +134,21 @@ export function commissionSettlementChannelOptions() {
|
|||||||
return request.get({ url: '/stats.commissionSettlement/channelOptions' })
|
return request.get({ url: '/stats.commissionSettlement/channelOptions' })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 提成核对:订单明细分页 bucket: 空|current|deferred */
|
/** 提成核对:订单明细分页 bucket: 空|current|deferred;appt_channel_value 可与 assistant_id/doctor_id 组合(0=未匹配挂号渠道) */
|
||||||
export function commissionSettlementOrderLines(params: {
|
export function commissionSettlementOrderLines(params: {
|
||||||
settlement_month: string
|
settlement_month: string
|
||||||
|
start_time?: string
|
||||||
|
end_time?: string
|
||||||
|
fulfillment_status?: number
|
||||||
|
require_system_auto_prescription?: 0 | 1
|
||||||
dept_ids?: number[] | string
|
dept_ids?: number[] | string
|
||||||
channel_code?: string
|
channel_code?: string
|
||||||
page?: number
|
page?: number
|
||||||
page_size?: number
|
page_size?: number
|
||||||
bucket?: string
|
bucket?: string
|
||||||
|
assistant_id?: number
|
||||||
|
doctor_id?: number
|
||||||
|
appt_channel_value?: number
|
||||||
}) {
|
}) {
|
||||||
return request.get(
|
return request.get(
|
||||||
{ url: '/stats.commissionSettlement/orderLines', params, timeout: COMMISSION_SETTLEMENT_TIMEOUT_MS },
|
{ url: '/stats.commissionSettlement/orderLines', params, timeout: COMMISSION_SETTLEMENT_TIMEOUT_MS },
|
||||||
@@ -160,3 +174,17 @@ export function commissionSettlementConfirmFinalize(params: Record<string, any>)
|
|||||||
{ ignoreCancelToken: true }
|
{ 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>>()
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -548,12 +557,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>
|
||||||
@@ -972,6 +989,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"
|
||||||
@@ -1436,6 +1495,7 @@ import {
|
|||||||
prescriptionLists,
|
prescriptionLists,
|
||||||
prescriptionAdd,
|
prescriptionAdd,
|
||||||
prescriptionEdit,
|
prescriptionEdit,
|
||||||
|
prescriptionPatchPatient,
|
||||||
prescriptionDelete,
|
prescriptionDelete,
|
||||||
prescriptionAudit,
|
prescriptionAudit,
|
||||||
prescriptionDetail,
|
prescriptionDetail,
|
||||||
@@ -1523,6 +1583,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 金额与确认 */
|
||||||
|
|||||||
@@ -252,7 +252,7 @@
|
|||||||
v-model="queryParams.assistant_id"
|
v-model="queryParams.assistant_id"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
placeholder="诊单医助"
|
placeholder="订单创建人"
|
||||||
class="!w-full"
|
class="!w-full"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
@@ -263,9 +263,22 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item class="w-[160px]" label="服务渠道">
|
||||||
|
<el-select v-model="queryParams.service_channel" clearable placeholder="全部" class="!w-full">
|
||||||
|
<el-option label="未指派" value="0" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" @click="resetPage">查询</el-button>
|
<el-button type="primary" @click="resetPage">查询</el-button>
|
||||||
<el-button @click="handleReset">重置</el-button>
|
<el-button @click="handleReset">重置</el-button>
|
||||||
|
<export-data
|
||||||
|
v-perms="['tcm.prescriptionOrder/export']"
|
||||||
|
class="ml-2.5"
|
||||||
|
:fetch-fun="prescriptionOrderExport"
|
||||||
|
:params="prescriptionOrderExportParams"
|
||||||
|
:page-size="pager.size"
|
||||||
|
export-hint="导出范围与上方筛选一致(履约状态、创建时间及其他条件均会生效)。含「自媒体渠道(挂号渠道来源)」:优先取该单关联处方登记的挂号;无则诊单下同患者挂号取 id 最大的一条(与前台挂号选择的记录一致);业绩侧栏带渠道筛选导出时与同页列表高亮挂号同源。"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -549,6 +562,28 @@
|
|||||||
round
|
round
|
||||||
class="ml-2"
|
class="ml-2"
|
||||||
>甘草 {{ detailData.gancao_reciperl_order_no }}</el-tag>
|
>甘草 {{ detailData.gancao_reciperl_order_no }}</el-tag>
|
||||||
|
<div v-if="detailData" class="flex items-center gap-2 ml-4 shrink-0">
|
||||||
|
<span class="text-sm text-gray-500">发货类型</span>
|
||||||
|
<el-radio-group
|
||||||
|
v-perms="['tcm.prescriptionOrder/setShipMode']"
|
||||||
|
:model-value="normalizeShipMode(detailData.ship_mode)"
|
||||||
|
size="small"
|
||||||
|
:disabled="shipModeSaving || !canEditShipMode(detailData)"
|
||||||
|
@change="onDetailShipModeChange"
|
||||||
|
>
|
||||||
|
<el-radio-button label="gancao">甘草药房</el-radio-button>
|
||||||
|
<el-radio-button
|
||||||
|
label="direct"
|
||||||
|
:disabled="isShipModeLockedToGancao(detailData)"
|
||||||
|
>洛阳药房</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-tag
|
||||||
|
v-if="!hasSetShipModePerm"
|
||||||
|
:type="normalizeShipMode(detailData.ship_mode) === 'direct' ? 'warning' : 'success'"
|
||||||
|
effect="plain"
|
||||||
|
round
|
||||||
|
>{{ shipModeLabel(detailData.ship_mode) }}</el-tag>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="detailData" class="flex items-center gap-2 mr-6">
|
<div v-if="detailData" class="flex items-center gap-2 mr-6">
|
||||||
<el-button
|
<el-button
|
||||||
@@ -1369,7 +1404,7 @@
|
|||||||
<!-- 编辑(分步向导与「创建业务订单」弹窗统一) -->
|
<!-- 编辑(分步向导与「创建业务订单」弹窗统一) -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="editVisible"
|
v-model="editVisible"
|
||||||
title="编辑业务订单"
|
:title="editGancaoLogisticsOnlyMode ? '修改物流信息(甘草订单)' : '编辑业务订单'"
|
||||||
width="940px"
|
width="940px"
|
||||||
top="4vh"
|
top="4vh"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@@ -1392,18 +1427,33 @@
|
|||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-steps
|
<el-alert
|
||||||
:active="editOrderStep"
|
v-if="editGancaoLogisticsOnlyMode"
|
||||||
finish-status="success"
|
type="info"
|
||||||
align-center
|
:closable="false"
|
||||||
class="create-order-steps"
|
show-icon
|
||||||
|
class="mb-4"
|
||||||
|
title="订单已提交甘草,仅可修改快递单号与承运商;地址、金额等变更请先走取消流程。"
|
||||||
>
|
>
|
||||||
<el-step title="患者与收货" description="诊单与物流地址" />
|
<template v-if="editGancaoDisplayNo" #default>
|
||||||
<el-step title="服务与支付单" description="套餐与关联收款" />
|
<div class="text-sm text-gray-700 mt-1">甘草处方单号:{{ editGancaoDisplayNo }}</div>
|
||||||
<el-step title="金额与确认" description="费用与备注" />
|
</template>
|
||||||
</el-steps>
|
</el-alert>
|
||||||
|
|
||||||
<p class="create-order-step-lead">{{ editOrderStepLead }}</p>
|
<template v-if="!editGancaoLogisticsOnlyMode">
|
||||||
|
<el-steps
|
||||||
|
:active="editOrderStep"
|
||||||
|
finish-status="success"
|
||||||
|
align-center
|
||||||
|
class="create-order-steps"
|
||||||
|
>
|
||||||
|
<el-step title="患者与收货" description="诊单与物流地址" />
|
||||||
|
<el-step title="服务与支付单" description="套餐与关联收款" />
|
||||||
|
<el-step title="金额与确认" description="费用与备注" />
|
||||||
|
</el-steps>
|
||||||
|
|
||||||
|
<p class="create-order-step-lead">{{ editOrderStepLead }}</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
<el-form
|
<el-form
|
||||||
ref="editFormRef"
|
ref="editFormRef"
|
||||||
@@ -1412,6 +1462,24 @@
|
|||||||
label-width="108px"
|
label-width="108px"
|
||||||
class="create-order-form"
|
class="create-order-form"
|
||||||
>
|
>
|
||||||
|
<template v-if="editGancaoLogisticsOnlyMode">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="物流设置">
|
||||||
|
<div class="flex gap-2 w-full">
|
||||||
|
<el-select v-model="editForm.express_company" placeholder="承运商" class="w-[140px] shrink-0">
|
||||||
|
<el-option label="自动识别" value="auto" />
|
||||||
|
<el-option label="顺丰速运" value="sf" />
|
||||||
|
<el-option label="京东快递" value="jd" />
|
||||||
|
<el-option label="极兔速递" value="jt" />
|
||||||
|
</el-select>
|
||||||
|
<el-input v-model="editForm.tracking_number" maxlength="80" placeholder="快递单号" class="flex-1 min-w-0" />
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
<template v-if="!editGancaoLogisticsOnlyMode">
|
||||||
<div v-show="editOrderStep === 0" class="create-order-step-panel">
|
<div v-show="editOrderStep === 0" class="create-order-step-panel">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@@ -1558,7 +1626,17 @@
|
|||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="服务渠道">
|
<el-form-item label="服务渠道">
|
||||||
<el-input v-model="editForm.service_channel" placeholder="可选" maxlength="100" />
|
<el-select
|
||||||
|
v-model="editForm.service_channel"
|
||||||
|
class="w-full"
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
clearable
|
||||||
|
placeholder="可选或自定义输入"
|
||||||
|
>
|
||||||
|
<el-option label="未指派" value="0" />
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@@ -1735,12 +1813,13 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="create-order-footer">
|
<div class="create-order-footer">
|
||||||
<div class="create-order-footer__hint">
|
<div v-if="!editGancaoLogisticsOnlyMode" class="create-order-footer__hint">
|
||||||
<span class="create-order-footer__step">第 {{ editOrderStep + 1 }} / 3 步</span>
|
<span class="create-order-footer__step">第 {{ editOrderStep + 1 }} / 3 步</span>
|
||||||
<span v-if="editOrderStep === 2 && editDepositMin > 0" class="create-order-footer__warn">
|
<span v-if="editOrderStep === 2 && editDepositMin > 0" class="create-order-footer__warn">
|
||||||
订单金额与关联支付单合计须 ≥ ¥{{ formatMoney(editDepositMin) }}
|
订单金额与关联支付单合计须 ≥ ¥{{ formatMoney(editDepositMin) }}
|
||||||
@@ -1748,17 +1827,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="create-order-footer__actions">
|
<div class="create-order-footer__actions">
|
||||||
<el-button size="large" @click="editVisible = false">取消</el-button>
|
<el-button size="large" @click="editVisible = false">取消</el-button>
|
||||||
<el-button v-if="editOrderStep > 0" size="large" @click="goEditOrderPrevStep">上一步</el-button>
|
<template v-if="!editGancaoLogisticsOnlyMode">
|
||||||
|
<el-button v-if="editOrderStep > 0" size="large" @click="goEditOrderPrevStep">上一步</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="editOrderStep < 2"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="goEditOrderNextStep"
|
||||||
|
>
|
||||||
|
下一步
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="editOrderStep < 2"
|
v-if="editGancaoLogisticsOnlyMode || editOrderStep === 2"
|
||||||
type="primary"
|
|
||||||
size="large"
|
|
||||||
@click="goEditOrderNextStep"
|
|
||||||
>
|
|
||||||
下一步
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-if="editOrderStep === 2"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
:loading="editSaving"
|
:loading="editSaving"
|
||||||
@@ -1866,10 +1947,12 @@
|
|||||||
/>
|
/>
|
||||||
<el-form v-loading="shipSaving" label-width="90px" class="pr-2" @submit.prevent="submitShip">
|
<el-form v-loading="shipSaving" label-width="90px" class="pr-2" @submit.prevent="submitShip">
|
||||||
<el-form-item label="发货方式">
|
<el-form-item label="发货方式">
|
||||||
<el-radio-group v-model="shipForm.ship_mode">
|
<el-tag
|
||||||
<el-radio label="gancao">甘草药方发</el-radio>
|
:type="normalizeShipMode(shipForm.ship_mode) === 'direct' ? 'warning' : 'success'"
|
||||||
<el-radio label="direct">药房直发</el-radio>
|
effect="plain"
|
||||||
</el-radio-group>
|
size="default"
|
||||||
|
>{{ shipDialogModeDisplay }}</el-tag>
|
||||||
|
<span class="text-xs text-gray-400 ml-2">请在订单详情顶部「发货类型」中设置,此处不可修改</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="承运商">
|
<el-form-item label="承运商">
|
||||||
<el-select v-model="shipForm.express_company" class="w-full">
|
<el-select v-model="shipForm.express_company" class="w-full">
|
||||||
@@ -1969,7 +2052,7 @@
|
|||||||
@closed="addPayOrderFormRef?.clearValidate()"
|
@closed="addPayOrderFormRef?.clearValidate()"
|
||||||
>
|
>
|
||||||
<el-alert
|
<el-alert
|
||||||
title="可以手动创建新支付单,或关联已存在但未绑定的支付单。"
|
:title="addPayOrderAlertTitle"
|
||||||
type="info"
|
type="info"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
show-icon
|
show-icon
|
||||||
@@ -1987,6 +2070,7 @@
|
|||||||
<el-radio-group v-model="addPayOrderForm.add_mode">
|
<el-radio-group v-model="addPayOrderForm.add_mode">
|
||||||
<el-radio value="create">手动创建</el-radio>
|
<el-radio value="create">手动创建</el-radio>
|
||||||
<el-radio value="link">关联已有</el-radio>
|
<el-radio value="link">关联已有</el-radio>
|
||||||
|
<el-radio value="completion_only">直接完单申请</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -2007,11 +2091,11 @@
|
|||||||
<el-form-item label="支付金额" prop="pay_amount">
|
<el-form-item label="支付金额" prop="pay_amount">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="addPayOrderForm.pay_amount"
|
v-model="addPayOrderForm.pay_amount"
|
||||||
:min="0.01"
|
:min="0"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
placeholder="请输入支付金额(元)"
|
placeholder="请输入支付金额(元,可为 0)"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备注" prop="pay_remark">
|
<el-form-item label="备注" prop="pay_remark">
|
||||||
@@ -2026,6 +2110,20 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 直接完单申请(不创建/关联支付单) -->
|
||||||
|
<template v-else-if="addPayOrderForm.add_mode === 'completion_only'">
|
||||||
|
<el-alert
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
class="!mb-0"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
不新增或关联支付单,仅向审核人员提交「完成订单」申请;提交后支付审核将变为待审核,审核通过后将自动结案。
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 关联已有模式 -->
|
<!-- 关联已有模式 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<el-form-item label="选择支付单" prop="link_pay_order_id">
|
<el-form-item label="选择支付单" prop="link_pay_order_id">
|
||||||
@@ -2056,8 +2154,8 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 完单申请选项 -->
|
<!-- 完单申请选项(创建/关联时可选) -->
|
||||||
<el-form-item label="完单申请">
|
<el-form-item v-if="addPayOrderForm.add_mode !== 'completion_only'" label="完单申请">
|
||||||
<el-radio-group v-model="addPayOrderForm.completion_request">
|
<el-radio-group v-model="addPayOrderForm.completion_request">
|
||||||
<el-radio :value="0">不申请</el-radio>
|
<el-radio :value="0">不申请</el-radio>
|
||||||
<el-radio :value="1">申请完成订单</el-radio>
|
<el-radio :value="1">申请完成订单</el-radio>
|
||||||
@@ -2069,7 +2167,13 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="addPayOrderVisible = false">取消</el-button>
|
<el-button @click="addPayOrderVisible = false">取消</el-button>
|
||||||
<el-button type="primary" :loading="addPayOrderSaving" @click="submitAddPayOrder">确认新增</el-button>
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="addPayOrderSaving"
|
||||||
|
@click="submitAddPayOrder"
|
||||||
|
>
|
||||||
|
{{ addPayOrderForm.add_mode === 'completion_only' ? '提交申请' : '确认新增' }}
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@@ -2287,7 +2391,7 @@
|
|||||||
<p v-if="rxPharmacyRemarkText" class="rx-text-warn">
|
<p v-if="rxPharmacyRemarkText" class="rx-text-warn">
|
||||||
药房备注:{{ rxPharmacyRemarkText }}
|
药房备注:{{ rxPharmacyRemarkText }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="rxOutPelletText" class="rx-text-warn">
|
<p v-if="rxOutPelletText && prescriptionViewData?.prescription_type !== '饮片'" class="rx-text-warn">
|
||||||
出丸:{{ rxOutPelletText }}
|
出丸:{{ rxOutPelletText }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -2505,13 +2609,16 @@ import {
|
|||||||
prescriptionOrderDetail,
|
prescriptionOrderDetail,
|
||||||
prescriptionOrderEdit,
|
prescriptionOrderEdit,
|
||||||
prescriptionOrderLists,
|
prescriptionOrderLists,
|
||||||
|
prescriptionOrderExport,
|
||||||
prescriptionOrderPaidPayOrders,
|
prescriptionOrderPaidPayOrders,
|
||||||
prescriptionOrderWithdraw,
|
prescriptionOrderWithdraw,
|
||||||
prescriptionOrderLogisticsTrace,
|
prescriptionOrderLogisticsTrace,
|
||||||
prescriptionOrderShip,
|
prescriptionOrderShip,
|
||||||
prescriptionOrderLogs,
|
prescriptionOrderLogs,
|
||||||
prescriptionOrderUpdateAmount,
|
prescriptionOrderUpdateAmount,
|
||||||
|
prescriptionOrderSetShipMode,
|
||||||
prescriptionOrderAddPayOrder,
|
prescriptionOrderAddPayOrder,
|
||||||
|
prescriptionOrderRequestCompletion,
|
||||||
prescriptionOrderComplete,
|
prescriptionOrderComplete,
|
||||||
prescriptionOrderRevokeRxAudit,
|
prescriptionOrderRevokeRxAudit,
|
||||||
prescriptionOrderRevokePayAudit,
|
prescriptionOrderRevokePayAudit,
|
||||||
@@ -2575,6 +2682,8 @@ const prescriptionOrderDetailButtonText = computed(() =>
|
|||||||
|
|
||||||
/** 与 server/config/project.php prescription_order_finance_roles 保持一致 */
|
/** 与 server/config/project.php prescription_order_finance_roles 保持一致 */
|
||||||
const FINANCE_ROLE_IDS = [0, 3, 6]
|
const FINANCE_ROLE_IDS = [0, 3, 6]
|
||||||
|
/** 与 server/config/project.php order_edit_all_roles 一致:可绕过「双审通过后创建人禁编」 */
|
||||||
|
const ORDER_EDIT_ALL_ROLE_IDS = [0, 3]
|
||||||
|
|
||||||
/** 判断当前用户是否有查看财务字段(内部成本)的权限 */
|
/** 判断当前用户是否有查看财务字段(内部成本)的权限 */
|
||||||
const canViewFinanceFields = () => {
|
const canViewFinanceFields = () => {
|
||||||
@@ -2780,15 +2889,17 @@ const queryParams = reactive({
|
|||||||
express_company: '' as '' | 'sf' | 'jd',
|
express_company: '' as '' | 'sf' | 'jd',
|
||||||
/** 开方医生(后台:关联处方 creator_id) */
|
/** 开方医生(后台:关联处方 creator_id) */
|
||||||
doctor_id: '' as number | '',
|
doctor_id: '' as number | '',
|
||||||
/** 诊单医助(后台:关联诊单 assistant_id) */
|
/** 订单创建人(后台:业务订单 creator_id) */
|
||||||
assistant_id: '' as number | '',
|
assistant_id: '' as number | '',
|
||||||
/** 诊单医助所属部门(后台:assistant_dept_id,la_admin_dept) */
|
/** 创建人所属部门(后台:assistant_dept_id,la_admin_dept) */
|
||||||
assistant_dept_id: '' as number | '',
|
assistant_dept_id: '' as number | '',
|
||||||
fulfillment_status: '' as number | '',
|
fulfillment_status: '' as number | '',
|
||||||
prescription_audit_status: '' as number | '',
|
prescription_audit_status: '' as number | '',
|
||||||
payment_slip_audit_status: '' as number | '',
|
payment_slip_audit_status: '' as number | '',
|
||||||
/** 供货方式:甘草(已传甘草药方单号)/ 自营(无甘草单号) */
|
/** 供货方式:甘草(已传甘草药方单号)/ 自营(无甘草单号) */
|
||||||
supply_mode: '' as '' | 'gancao' | 'self',
|
supply_mode: '' as '' | 'gancao' | 'self',
|
||||||
|
/** 服务渠道:'' 不限;'0' 未指派(库内 '' 或 '0') */
|
||||||
|
service_channel: '' as '' | '0',
|
||||||
/** 列表排除履约已取消(4):重点看板「待处方审核」等用 */
|
/** 列表排除履约已取消(4):重点看板「待处方审核」等用 */
|
||||||
exclude_fulfillment_cancelled: 0 as number
|
exclude_fulfillment_cancelled: 0 as number
|
||||||
})
|
})
|
||||||
@@ -2853,21 +2964,35 @@ function handleFocusBoardSearch(key: 'pendingRx' | 'pendingPay' | 'pendingShip'
|
|||||||
resetPage()
|
resetPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchLists(params: Record<string, unknown>) {
|
function normalizePrescriptionOrderListQuery(params: Record<string, unknown>): Record<string, unknown> {
|
||||||
const p: Record<string, unknown> = { ...params }
|
const p: Record<string, unknown> = { ...params }
|
||||||
if (p.prescription_id === '' || p.prescription_id === undefined) {
|
if (p.prescription_id === '' || p.prescription_id === undefined) {
|
||||||
delete p.prescription_id
|
delete p.prescription_id
|
||||||
} else {
|
} else {
|
||||||
p.prescription_id = Number(p.prescription_id)
|
p.prescription_id = Number(p.prescription_id)
|
||||||
}
|
}
|
||||||
if (p.fulfillment_status === '' || p.fulfillment_status === undefined) {
|
if (p.fulfillment_status === '' || p.fulfillment_status === undefined || p.fulfillment_status === null) {
|
||||||
delete p.fulfillment_status
|
delete p.fulfillment_status
|
||||||
|
} else {
|
||||||
|
p.fulfillment_status = Number(p.fulfillment_status)
|
||||||
}
|
}
|
||||||
if (p.prescription_audit_status === '' || p.prescription_audit_status === undefined) {
|
if (
|
||||||
|
p.prescription_audit_status === '' ||
|
||||||
|
p.prescription_audit_status === undefined ||
|
||||||
|
p.prescription_audit_status === null
|
||||||
|
) {
|
||||||
delete p.prescription_audit_status
|
delete p.prescription_audit_status
|
||||||
|
} else {
|
||||||
|
p.prescription_audit_status = Number(p.prescription_audit_status)
|
||||||
}
|
}
|
||||||
if (p.payment_slip_audit_status === '' || p.payment_slip_audit_status === undefined) {
|
if (
|
||||||
|
p.payment_slip_audit_status === '' ||
|
||||||
|
p.payment_slip_audit_status === undefined ||
|
||||||
|
p.payment_slip_audit_status === null
|
||||||
|
) {
|
||||||
delete p.payment_slip_audit_status
|
delete p.payment_slip_audit_status
|
||||||
|
} else {
|
||||||
|
p.payment_slip_audit_status = Number(p.payment_slip_audit_status)
|
||||||
}
|
}
|
||||||
if (!p.supply_mode || p.supply_mode === '') {
|
if (!p.supply_mode || p.supply_mode === '') {
|
||||||
delete p.supply_mode
|
delete p.supply_mode
|
||||||
@@ -2891,12 +3016,17 @@ async function fetchLists(params: Record<string, unknown>) {
|
|||||||
if (!String(p.patient_keyword || '').trim()) delete p.patient_keyword
|
if (!String(p.patient_keyword || '').trim()) delete p.patient_keyword
|
||||||
if (!String(p.express_keyword || '').trim()) delete p.express_keyword
|
if (!String(p.express_keyword || '').trim()) delete p.express_keyword
|
||||||
if (!p.express_company || p.express_company === '') delete p.express_company
|
if (!p.express_company || p.express_company === '') delete p.express_company
|
||||||
|
if (p.service_channel === '' || p.service_channel === undefined || p.service_channel === null) {
|
||||||
|
delete p.service_channel
|
||||||
|
} else {
|
||||||
|
p.service_channel = String(p.service_channel).trim()
|
||||||
|
if (p.service_channel === '') delete p.service_channel
|
||||||
|
}
|
||||||
if (Number(p.exclude_fulfillment_cancelled) !== 1) delete p.exclude_fulfillment_cancelled
|
if (Number(p.exclude_fulfillment_cancelled) !== 1) delete p.exclude_fulfillment_cancelled
|
||||||
if (!String(p.start_time || '').trim() || !String(p.end_time || '').trim()) {
|
if (!String(p.start_time || '').trim() || !String(p.end_time || '').trim()) {
|
||||||
delete p.start_time
|
delete p.start_time
|
||||||
delete p.end_time
|
delete p.end_time
|
||||||
} else {
|
} else {
|
||||||
// 当前页面按“日”筛选:请求接口时展开为整天区间,避免结束日只落在 00:00:00。
|
|
||||||
const st = String(p.start_time || '').trim()
|
const st = String(p.start_time || '').trim()
|
||||||
const et = String(p.end_time || '').trim()
|
const et = String(p.end_time || '').trim()
|
||||||
if (/^\d{4}-\d{2}-\d{2}$/.test(st) && /^\d{4}-\d{2}-\d{2}$/.test(et)) {
|
if (/^\d{4}-\d{2}-\d{2}$/.test(st) && /^\d{4}-\d{2}-\d{2}$/.test(et)) {
|
||||||
@@ -2904,9 +3034,16 @@ async function fetchLists(params: Record<string, unknown>) {
|
|||||||
p.end_time = `${et} 23:59:59`
|
p.end_time = `${et} 23:59:59`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLists(params: Record<string, unknown>) {
|
||||||
|
const p = normalizePrescriptionOrderListQuery({ ...params })
|
||||||
return prescriptionOrderLists(p)
|
return prescriptionOrderLists(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const prescriptionOrderExportParams = computed(() => normalizePrescriptionOrderListQuery({ ...queryParams }))
|
||||||
|
|
||||||
const { pager, getLists, resetPage, resetParams } = usePaging({
|
const { pager, getLists, resetPage, resetParams } = usePaging({
|
||||||
fetchFun: fetchLists,
|
fetchFun: fetchLists,
|
||||||
params: queryParams
|
params: queryParams
|
||||||
@@ -3102,6 +3239,7 @@ function handleReset() {
|
|||||||
queryParams.prescription_audit_status = ''
|
queryParams.prescription_audit_status = ''
|
||||||
queryParams.payment_slip_audit_status = ''
|
queryParams.payment_slip_audit_status = ''
|
||||||
queryParams.supply_mode = ''
|
queryParams.supply_mode = ''
|
||||||
|
queryParams.service_channel = ''
|
||||||
queryParams.exclude_fulfillment_cancelled = 0
|
queryParams.exclude_fulfillment_cancelled = 0
|
||||||
resetParams()
|
resetParams()
|
||||||
}
|
}
|
||||||
@@ -3200,19 +3338,59 @@ function formatTime(v: unknown) {
|
|||||||
return String(v)
|
return String(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// 已完成(3)、已取消(4) 不可编辑
|
||||||
// 已成功提交甘草(已生成甘草处方单号 或 提交时间 > 0)不允许再编辑
|
if (fs === 3 || fs === 4) return false
|
||||||
|
|
||||||
|
// 双审均已通过:仅订单创建人不可再编辑(超管/全量编辑角色仍可改)
|
||||||
|
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
|
|
||||||
|
// 甘草已提交:仅允许在待双审/履约中/已发货/进行中下修改快递(已签收 6 不可改单号;后端 edit 亦拦截)
|
||||||
|
if (gcLocked) {
|
||||||
|
return [1, 2, 5, 7].includes(fs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未提交甘草:仅待双审(1)、履约中(2)可全量编辑
|
||||||
|
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 }) {
|
||||||
@@ -3268,6 +3446,70 @@ function canShipRow(row: { fulfillment_status?: number }) {
|
|||||||
return Number(row.fulfillment_status) === 2
|
return Number(row.fulfillment_status) === 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ShipMode = 'gancao' | 'direct'
|
||||||
|
|
||||||
|
const hasSetShipModePerm = computed(() => {
|
||||||
|
const p = userStore.perms || []
|
||||||
|
return p.includes('*') || p.includes('tcm.prescriptionOrder/setShipMode')
|
||||||
|
})
|
||||||
|
|
||||||
|
function normalizeShipMode(v: unknown): ShipMode {
|
||||||
|
return String(v || '').toLowerCase() === 'direct' ? 'direct' : 'gancao'
|
||||||
|
}
|
||||||
|
|
||||||
|
function shipModeLabel(v: unknown): string {
|
||||||
|
return normalizeShipMode(v) === 'direct' ? '洛阳药房' : '甘草药房'
|
||||||
|
}
|
||||||
|
|
||||||
|
function isShipModeLockedToGancao(row: { gancao_reciperl_order_no?: string | null }) {
|
||||||
|
return String(row.gancao_reciperl_order_no || '').trim() !== ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function canEditShipMode(row: { fulfillment_status?: number }) {
|
||||||
|
const fs = Number(row.fulfillment_status)
|
||||||
|
return fs !== 3 && fs !== 4
|
||||||
|
}
|
||||||
|
|
||||||
|
const shipModeSaving = ref(false)
|
||||||
|
|
||||||
|
function isSamePrescriptionOrderId(a: unknown, b: unknown) {
|
||||||
|
const na = Number(a)
|
||||||
|
const nb = Number(b)
|
||||||
|
return na > 0 && na === nb
|
||||||
|
}
|
||||||
|
|
||||||
|
const shipDialogModeDisplay = computed(() => {
|
||||||
|
return normalizeShipMode(shipForm.ship_mode) === 'direct' ? '洛阳药房直发' : '甘草药房直发'
|
||||||
|
})
|
||||||
|
|
||||||
|
async function onDetailShipModeChange(mode: string | number | boolean | undefined) {
|
||||||
|
const row = detailData.value
|
||||||
|
if (!row?.id) return
|
||||||
|
const next = normalizeShipMode(mode)
|
||||||
|
const prev = normalizeShipMode(row.ship_mode)
|
||||||
|
if (next === prev) return
|
||||||
|
if (!canEditShipMode(row)) {
|
||||||
|
feedback.msgWarning('当前订单状态不可修改发货类型')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shipModeSaving.value = true
|
||||||
|
try {
|
||||||
|
await prescriptionOrderSetShipMode({ id: row.id, ship_mode: next })
|
||||||
|
if (detailData.value) detailData.value.ship_mode = next
|
||||||
|
if (shipVisible.value && isSamePrescriptionOrderId(shipRowId.value, row.id)) {
|
||||||
|
shipForm.ship_mode = next
|
||||||
|
}
|
||||||
|
await refreshCurrentPrescriptionOrderDetail()
|
||||||
|
feedback.msgSuccess('发货类型已保存')
|
||||||
|
getLists()
|
||||||
|
void fetchLogs(row.id)
|
||||||
|
} catch {
|
||||||
|
/* 拦截器已提示 */
|
||||||
|
} finally {
|
||||||
|
shipModeSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function canAddPayOrderRow(row: { fulfillment_status?: number }) {
|
function canAddPayOrderRow(row: { fulfillment_status?: number }) {
|
||||||
// 已发货(5) / 已签收(6) 状态可补齐支付单
|
// 已发货(5) / 已签收(6) 状态可补齐支付单
|
||||||
const fs = Number(row.fulfillment_status)
|
const fs = Number(row.fulfillment_status)
|
||||||
@@ -3281,9 +3523,8 @@ function canCompleteRow(row: { fulfillment_status?: number; payment_slip_audit_s
|
|||||||
}
|
}
|
||||||
|
|
||||||
function canQuickTrackRow(row: { fulfillment_status?: number }) {
|
function canQuickTrackRow(row: { fulfillment_status?: number }) {
|
||||||
// 已发货(5) / 已签收(6) 后如需修改快递信息可单独使用此功能
|
// 仅已发货(5) 可修改快递单号;已签收(6) 不可再改
|
||||||
const fs = Number(row.fulfillment_status)
|
return Number(row.fulfillment_status) === 5
|
||||||
return fs === 5 || fs === 6
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function canUpdateAmount(row: { id?: number; fulfillment_status?: number } | null | undefined) {
|
function canUpdateAmount(row: { id?: number; fulfillment_status?: number } | null | undefined) {
|
||||||
@@ -3627,6 +3868,8 @@ function logActionText(act: string) {
|
|||||||
ship: '确认发货',
|
ship: '确认发货',
|
||||||
withdraw: '撤销',
|
withdraw: '撤销',
|
||||||
link_pay_order: '关联支付单',
|
link_pay_order: '关联支付单',
|
||||||
|
completion_request: '完单申请',
|
||||||
|
auto_complete: '自动完成',
|
||||||
revoke_rx_audit: '撤回处方审核',
|
revoke_rx_audit: '撤回处方审核',
|
||||||
revoke_pay_audit: '撤回支付审核',
|
revoke_pay_audit: '撤回支付审核',
|
||||||
gancao_submit: '甘草下单',
|
gancao_submit: '甘草下单',
|
||||||
@@ -3919,6 +4162,10 @@ const editSaving = ref(false)
|
|||||||
const editOrderStep = ref(0)
|
const editOrderStep = ref(0)
|
||||||
/** 顶部「关联处方」卡片数据(来自业务订单详情中的 prescription) */
|
/** 顶部「关联处方」卡片数据(来自业务订单详情中的 prescription) */
|
||||||
const editOrderPrescription = ref<Record<string, any> | null>(null)
|
const editOrderPrescription = ref<Record<string, any> | null>(null)
|
||||||
|
/** 甘草 SCM 已提交:弹窗仅展示并提交快递单号与承运商 */
|
||||||
|
const editGancaoLogisticsOnlyMode = ref(false)
|
||||||
|
/** 用于提示文案展示甘草处方单号 */
|
||||||
|
const editGancaoDisplayNo = ref('')
|
||||||
|
|
||||||
const editOrderStepLead = computed(() => {
|
const editOrderStepLead = computed(() => {
|
||||||
const texts = [
|
const texts = [
|
||||||
@@ -4097,6 +4344,8 @@ const editRules = computed<FormRules>(() => {
|
|||||||
function resetEditOrderDialog() {
|
function resetEditOrderDialog() {
|
||||||
editOrderStep.value = 0
|
editOrderStep.value = 0
|
||||||
editOrderPrescription.value = null
|
editOrderPrescription.value = null
|
||||||
|
editGancaoLogisticsOnlyMode.value = false
|
||||||
|
editGancaoDisplayNo.value = ''
|
||||||
editFormRef.value?.clearValidate()
|
editFormRef.value?.clearValidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4123,7 +4372,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
|
||||||
@@ -4217,6 +4484,11 @@ async function openEdit(row: { id: number }) {
|
|||||||
editForm.internal_cost =
|
editForm.internal_cost =
|
||||||
d.internal_cost != null && d.internal_cost !== '' ? Number(d.internal_cost) : undefined
|
d.internal_cost != null && d.internal_cost !== '' ? Number(d.internal_cost) : undefined
|
||||||
|
|
||||||
|
const gcNo = String(d.gancao_reciperl_order_no || '').trim()
|
||||||
|
const gcTime = Number(d.gancao_submit_time || 0)
|
||||||
|
editGancaoLogisticsOnlyMode.value = gcNo !== '' || gcTime > 0
|
||||||
|
editGancaoDisplayNo.value = gcNo || ''
|
||||||
|
|
||||||
await loadEditPaidOrders(editForm.diagnosis_id, editForm.id)
|
await loadEditPaidOrders(editForm.diagnosis_id, editForm.id)
|
||||||
|
|
||||||
// BUG FIX: NextTick 清理刚展开时意外触发的金额下限表单校验红字
|
// BUG FIX: NextTick 清理刚展开时意外触发的金额下限表单校验红字
|
||||||
@@ -4232,8 +4504,14 @@ async function openEdit(row: { id: number }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submitEdit() {
|
async function submitEdit() {
|
||||||
if (!editFormRef.value || editOrderStep.value !== 2) return
|
if (!editFormRef.value) return
|
||||||
await editFormRef.value.validate()
|
const gancaoLogisticsOnly = editGancaoLogisticsOnlyMode.value
|
||||||
|
if (!gancaoLogisticsOnly && editOrderStep.value !== 2) return
|
||||||
|
if (!gancaoLogisticsOnly) {
|
||||||
|
await editFormRef.value.validate()
|
||||||
|
} else {
|
||||||
|
editFormRef.value.clearValidate()
|
||||||
|
}
|
||||||
editSaving.value = true
|
editSaving.value = true
|
||||||
try {
|
try {
|
||||||
const payload: Record<string, unknown> = {
|
const payload: Record<string, unknown> = {
|
||||||
@@ -4435,6 +4713,61 @@ async function testGancaoPreviewFromDetail() {
|
|||||||
if (d && d.success) {
|
if (d && d.success) {
|
||||||
gancaoPreviewData.value = d
|
gancaoPreviewData.value = d
|
||||||
gancaoPreviewDrawerVisible.value = true
|
gancaoPreviewDrawerVisible.value = true
|
||||||
|
|
||||||
|
// 将价格信息「总计」(药材成本 + 制作费 + 物流费) 写入 internal_cost(需财务字段权限,与后台 canViewInternalCost 一致)
|
||||||
|
if (canViewFinanceFields() && d.fee) {
|
||||||
|
const internalTotal = calculateTotalFee(d.fee)
|
||||||
|
try {
|
||||||
|
const resDetail: any = await prescriptionOrderDetail({ id: detailData.value.id })
|
||||||
|
const od = resDetail?.data ?? resDetail
|
||||||
|
if (od?.id) {
|
||||||
|
const pkgRaw = od.service_package
|
||||||
|
const servicePackageStr = Array.isArray(pkgRaw)
|
||||||
|
? pkgRaw.join(',')
|
||||||
|
: String(pkgRaw || '')
|
||||||
|
await prescriptionOrderEdit({
|
||||||
|
id: od.id,
|
||||||
|
recipient_name: od.recipient_name || '',
|
||||||
|
recipient_phone: od.recipient_phone || '',
|
||||||
|
shipping_province: od.shipping_province || '',
|
||||||
|
shipping_city: od.shipping_city || '',
|
||||||
|
shipping_district: od.shipping_district || '',
|
||||||
|
shipping_address: od.shipping_address || '',
|
||||||
|
is_follow_up: od.is_follow_up ? 1 : 0,
|
||||||
|
medication_days:
|
||||||
|
od.medication_days != null && String(od.medication_days).trim() !== ''
|
||||||
|
? od.medication_days
|
||||||
|
: '',
|
||||||
|
dose_unit: od.dose_unit || '剂',
|
||||||
|
dose_count: od.dose_count > 0 ? od.dose_count : 1,
|
||||||
|
prev_staff: od.prev_staff || '',
|
||||||
|
service_channel: od.service_channel || '',
|
||||||
|
service_package: servicePackageStr,
|
||||||
|
express_company: od.express_company || 'auto',
|
||||||
|
tracking_number: od.tracking_number || '',
|
||||||
|
fee_type: Number(od.fee_type) || 3,
|
||||||
|
amount: Number(od.amount) || 0,
|
||||||
|
remark_extra: od.remark_extra || '',
|
||||||
|
remark_assistant: od.remark_assistant || '',
|
||||||
|
internal_cost: internalTotal,
|
||||||
|
pay_order_ids: Array.isArray(od.pay_order_ids) ? od.pay_order_ids : []
|
||||||
|
})
|
||||||
|
feedback.msgSuccess('内部成本已按预报价总计更新')
|
||||||
|
getLists()
|
||||||
|
if (detailVisible.value && Number(detailData.value?.id) === Number(od.id)) {
|
||||||
|
try {
|
||||||
|
const r: any = await prescriptionOrderDetail({ id: od.id })
|
||||||
|
const nd = r?.data ?? r ?? null
|
||||||
|
if (nd) detailData.value = nd
|
||||||
|
} catch {
|
||||||
|
detailData.value = { ...detailData.value, internal_cost: internalTotal }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* 拦截器已提示;仍保留抽屉中的预报价结果 */
|
||||||
|
}
|
||||||
|
}
|
||||||
//feedback.msgSuccess('预下单测试成功')
|
//feedback.msgSuccess('预下单测试成功')
|
||||||
} else {
|
} else {
|
||||||
// feedback.msgError(d?.error || '预下单失败')
|
// feedback.msgError(d?.error || '预下单失败')
|
||||||
@@ -4550,8 +4883,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}」已保存,是否立即确认发货?`,
|
||||||
@@ -4578,14 +4911,31 @@ const shipForm = reactive({
|
|||||||
tracking_number: ''
|
tracking_number: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function resolveShipModeForRow(row: { id: number; ship_mode?: unknown }) {
|
||||||
|
if (isSamePrescriptionOrderId(detailData.value?.id, row.id)) {
|
||||||
|
return normalizeShipMode(detailData.value!.ship_mode)
|
||||||
|
}
|
||||||
|
return normalizeShipMode(row.ship_mode)
|
||||||
|
}
|
||||||
|
|
||||||
function openShip(row: { id: number; express_company?: unknown; tracking_number?: unknown; ship_mode?: unknown }) {
|
function openShip(row: { id: number; express_company?: unknown; tracking_number?: unknown; ship_mode?: unknown }) {
|
||||||
shipRowId.value = row.id
|
shipRowId.value = Number(row.id)
|
||||||
shipForm.ship_mode = String(row.ship_mode || 'gancao') || 'gancao'
|
shipForm.ship_mode = resolveShipModeForRow(row)
|
||||||
shipForm.express_company = String(row.express_company || 'auto') || 'auto'
|
shipForm.express_company = String(row.express_company || 'auto') || 'auto'
|
||||||
shipForm.tracking_number = String(row.tracking_number || '')
|
shipForm.tracking_number = String(row.tracking_number || '')
|
||||||
shipVisible.value = true
|
shipVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => detailData.value?.ship_mode,
|
||||||
|
(v) => {
|
||||||
|
if (!shipVisible.value || !shipRowId.value || !isSamePrescriptionOrderId(detailData.value?.id, shipRowId.value)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shipForm.ship_mode = normalizeShipMode(v)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
async function submitShip() {
|
async function submitShip() {
|
||||||
if (!shipForm.tracking_number.trim()) {
|
if (!shipForm.tracking_number.trim()) {
|
||||||
feedback.msgError('请先填写快递单号再确认发货')
|
feedback.msgError('请先填写快递单号再确认发货')
|
||||||
@@ -4595,7 +4945,7 @@ async function submitShip() {
|
|||||||
try {
|
try {
|
||||||
await prescriptionOrderShip({
|
await prescriptionOrderShip({
|
||||||
id: shipRowId.value,
|
id: shipRowId.value,
|
||||||
ship_mode: shipForm.ship_mode === 'direct' ? 'direct' : 'gancao',
|
ship_mode: normalizeShipMode(shipForm.ship_mode),
|
||||||
express_company: shipForm.express_company || 'auto',
|
express_company: shipForm.express_company || 'auto',
|
||||||
tracking_number: shipForm.tracking_number.trim()
|
tracking_number: shipForm.tracking_number.trim()
|
||||||
})
|
})
|
||||||
@@ -4693,8 +5043,15 @@ const addPayOrderRowId = ref(0)
|
|||||||
const addPayOrderFormRef = ref<FormInstance>()
|
const addPayOrderFormRef = ref<FormInstance>()
|
||||||
const addPayOrderAvailableLoading = ref(false)
|
const addPayOrderAvailableLoading = ref(false)
|
||||||
const addPayOrderAvailableList = ref<any[]>([])
|
const addPayOrderAvailableList = ref<any[]>([])
|
||||||
|
const addPayOrderAlertTitle = computed(() => {
|
||||||
|
if (addPayOrderForm.add_mode === 'completion_only') {
|
||||||
|
return '不创建或关联支付单,仅提交完单申请,由审核人员在支付审核时处理。'
|
||||||
|
}
|
||||||
|
return '可以手动创建新支付单,或关联已存在但未绑定的支付单。'
|
||||||
|
})
|
||||||
|
|
||||||
const addPayOrderForm = reactive({
|
const addPayOrderForm = reactive({
|
||||||
add_mode: 'create' as 'create' | 'link',
|
add_mode: 'create' as 'create' | 'link' | 'completion_only',
|
||||||
order_type: 3,
|
order_type: 3,
|
||||||
pay_amount: undefined as number | undefined,
|
pay_amount: undefined as number | undefined,
|
||||||
pay_remark: '',
|
pay_remark: '',
|
||||||
@@ -4702,38 +5059,47 @@ const addPayOrderForm = reactive({
|
|||||||
completion_request: 0
|
completion_request: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
const addPayOrderRules: FormRules = {
|
const addPayOrderRules = computed<FormRules>(() => {
|
||||||
add_mode: [{ required: true, message: '请选择添加方式', trigger: 'change' }],
|
const rules: FormRules = {
|
||||||
order_type: [{ required: true, message: '请选择费用类别', trigger: 'change' }],
|
add_mode: [{ required: true, message: '请选择添加方式', trigger: 'change' }]
|
||||||
pay_amount: [
|
}
|
||||||
{ required: true, message: '请输入支付金额', trigger: 'blur' },
|
if (addPayOrderForm.add_mode === 'create') {
|
||||||
{
|
rules.order_type = [{ required: true, message: '请选择费用类别', trigger: 'change' }]
|
||||||
validator: (_rule, v, cb) => {
|
rules.pay_amount = [
|
||||||
const n = Number(v)
|
{
|
||||||
if (!Number.isFinite(n) || n <= 0) {
|
validator: (_rule, v, cb) => {
|
||||||
cb(new Error('金额须大于 0'))
|
if (v === undefined || v === null || v === '') {
|
||||||
} else {
|
cb(new Error('请输入支付金额'))
|
||||||
cb()
|
return
|
||||||
}
|
}
|
||||||
},
|
const n = Number(v)
|
||||||
trigger: 'blur'
|
if (!Number.isFinite(n) || n < 0) {
|
||||||
}
|
cb(new Error('金额不能小于 0'))
|
||||||
],
|
} else {
|
||||||
link_pay_order_ids: [
|
cb()
|
||||||
{
|
}
|
||||||
required: true,
|
},
|
||||||
message: '请至少选择一个支付单',
|
trigger: 'blur'
|
||||||
trigger: 'change',
|
}
|
||||||
validator: (_rule, v, cb) => {
|
]
|
||||||
if (!Array.isArray(v) || v.length === 0) {
|
} else if (addPayOrderForm.add_mode === 'link') {
|
||||||
cb(new Error('请至少选择一个支付单'))
|
rules.link_pay_order_ids = [
|
||||||
} else {
|
{
|
||||||
cb()
|
required: true,
|
||||||
|
message: '请至少选择一个支付单',
|
||||||
|
trigger: 'change',
|
||||||
|
validator: (_rule, v, cb) => {
|
||||||
|
if (!Array.isArray(v) || v.length === 0) {
|
||||||
|
cb(new Error('请至少选择一个支付单'))
|
||||||
|
} else {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
]
|
}
|
||||||
}
|
return rules
|
||||||
|
})
|
||||||
|
|
||||||
async function loadAddPayOrderAvailable(diagnosisId: number, currentLinkedIds: number[] = []) {
|
async function loadAddPayOrderAvailable(diagnosisId: number, currentLinkedIds: number[] = []) {
|
||||||
if (!diagnosisId) {
|
if (!diagnosisId) {
|
||||||
@@ -4781,7 +5147,10 @@ async function submitAddPayOrder() {
|
|||||||
await addPayOrderFormRef.value.validate()
|
await addPayOrderFormRef.value.validate()
|
||||||
addPayOrderSaving.value = true
|
addPayOrderSaving.value = true
|
||||||
try {
|
try {
|
||||||
if (addPayOrderForm.add_mode === 'create') {
|
if (addPayOrderForm.add_mode === 'completion_only') {
|
||||||
|
await prescriptionOrderRequestCompletion({ id: addPayOrderRowId.value })
|
||||||
|
feedback.msgSuccess('完单申请已提交,请等待支付审核')
|
||||||
|
} else if (addPayOrderForm.add_mode === 'create') {
|
||||||
// 手动创建新支付单
|
// 手动创建新支付单
|
||||||
await prescriptionOrderAddPayOrder({
|
await prescriptionOrderAddPayOrder({
|
||||||
id: addPayOrderRowId.value,
|
id: addPayOrderRowId.value,
|
||||||
@@ -5026,6 +5395,8 @@ const rxTypeText = computed(() => {
|
|||||||
const v = prescriptionViewData.value as any
|
const v = prescriptionViewData.value as any
|
||||||
if (!v) return '—'
|
if (!v) return '—'
|
||||||
const t = v.prescription_type || '浓缩水丸'
|
const t = v.prescription_type || '浓缩水丸'
|
||||||
|
/** 饮片不匹配「丸|散|膏|片」,避免误显示「浓缩丸-饮片」;类型字段直接展示饮片 */
|
||||||
|
if (t === '饮片') return '饮片'
|
||||||
return /丸|散|膏|片/.test(t) ? `浓缩丸-${t}` : t
|
return /丸|散|膏|片/.test(t) ? `浓缩丸-${t}` : t
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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: {
|
||||||
@@ -3887,7 +3923,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 +4366,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}」已保存,是否立即确认发货?`,
|
||||||
|
|||||||
@@ -9,7 +9,11 @@
|
|||||||
</span>
|
</span>
|
||||||
<h1 class="cs-masthead__title">提成结算业绩</h1>
|
<h1 class="cs-masthead__title">提成结算业绩</h1>
|
||||||
<p class="cs-masthead__desc">
|
<p class="cs-masthead__desc">
|
||||||
系统代开处方对应的已完成业务订单;按结算月 7 日截止拆分本期提成与顺延下期;可按部门汇总或下发至业绩归属医助与处方开方医生
|
订单池默认与<strong>处方订单列表</strong>同源:<strong>创建时间 start_time~end_time</strong> +
|
||||||
|
<strong>fulfillment_status</strong>(默认完成态 3),账号<strong>列表可见性/数据域</strong>一并生效。
|
||||||
|
选结算月<strong>仍可</strong>套用「物流签收<strong>与</strong>尾款支付<strong>均须在</strong><strong>结算月 7 日 24 点</strong>前」拆分「本期提成 / 顺延下期」
|
||||||
|
(仅有签收在范围内、尾款晚于截止仍会顺延);
|
||||||
|
展示部门/渠道下拉与列表不同,勾选后会与列表<strong>计数不同</strong>。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<dl v-if="meta.settlement_month || meta.cutoff_end" class="cs-masthead__stats">
|
<dl v-if="meta.settlement_month || meta.cutoff_end" class="cs-masthead__stats">
|
||||||
@@ -17,9 +21,9 @@
|
|||||||
<dt class="cs-stat__label">结算月</dt>
|
<dt class="cs-stat__label">结算月</dt>
|
||||||
<dd class="cs-stat__value">{{ meta.settlement_month || settlementMonth }}</dd>
|
<dd class="cs-stat__value">{{ meta.settlement_month || settlementMonth }}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="meta.order_month" class="cs-stat">
|
<div v-if="meta.order_month || meta.order_start_time" class="cs-stat">
|
||||||
<dt class="cs-stat__label">订单创建月</dt>
|
<dt class="cs-stat__label">订单时间窗</dt>
|
||||||
<dd class="cs-stat__value">{{ meta.order_month }}</dd>
|
<dd class="cs-stat__value cs-stat__value--xs">{{ orderWindowLabel }}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="meta.cutoff_end" class="cs-stat">
|
<div v-if="meta.cutoff_end" class="cs-stat">
|
||||||
<dt class="cs-stat__label">截止时刻</dt>
|
<dt class="cs-stat__label">截止时刻</dt>
|
||||||
@@ -42,6 +46,21 @@
|
|||||||
class="filter-month"
|
class="filter-month"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="filter-field filter-field--rangewrap">
|
||||||
|
<span class="lbl">订单创建</span>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="orderTimeRange"
|
||||||
|
type="datetimerange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="列表 start_time"
|
||||||
|
end-placeholder="列表 end_time"
|
||||||
|
class="filter-datetimerange"
|
||||||
|
/>
|
||||||
|
<el-checkbox v-model="requireSystemAutoOnly" class="filter-sa-check">
|
||||||
|
仅系统代开处方
|
||||||
|
</el-checkbox>
|
||||||
|
</div>
|
||||||
<div class="filter-field">
|
<div class="filter-field">
|
||||||
<span class="lbl">渠道来源</span>
|
<span class="lbl">渠道来源</span>
|
||||||
<el-select
|
<el-select
|
||||||
@@ -117,17 +136,26 @@
|
|||||||
<div class="cs-alert__body">
|
<div class="cs-alert__body">
|
||||||
<p v-if="meta.rule_note">{{ meta.rule_note }}</p>
|
<p v-if="meta.rule_note">{{ meta.rule_note }}</p>
|
||||||
<p v-else>
|
<p v-else>
|
||||||
订单创建月为<strong>结算月的上一自然月</strong>;仅<strong>系统代开处方</strong>(完成挂号等自动生成)对应的<strong>履约已完成</strong>业务订单。
|
请选择结算月并设置<strong>订单创建时间范围</strong>(与处方订单列表 URL 参数
|
||||||
<strong>签收</strong>时间取物流签收时间;<strong>尾款支付</strong>取关联支付单已支付时间(优先尾款/全部费用类型)。
|
<code>start_time</code>/<code>end_time</code>/<code>fulfillment_status</code>
|
||||||
签收与尾款均在<strong>结算月 7 日 24:00 前</strong>完成的计入「本期提成」,其余计入「顺延下期」。
|
一致);切换结算月会自动带出<strong>上一自然月整段</strong>作为默认时段,可按业务修改(例如 2026-04-02~04-30)。
|
||||||
|
「仅系统代开处方」勾选后与列表在未限制开方类型时的条数会不一致。
|
||||||
</p>
|
</p>
|
||||||
<p v-if="meta.cutoff_end" class="cs-alert__meta">
|
<p v-if="meta.cutoff_end" class="cs-alert__meta">
|
||||||
当前结算月:<strong>{{ meta.settlement_month }}</strong>
|
当前结算月:<strong>{{ meta.settlement_month }}</strong>
|
||||||
· 订单创建月:<strong>{{ meta.order_month }}</strong>
|
· 订单时间:<strong>{{ orderWindowLabel }}</strong>
|
||||||
|
· 履约状态:<strong>{{ meta.fulfillment_status ?? 3 }}</strong>
|
||||||
· 截止时刻:<strong>{{ meta.cutoff_end }}</strong>
|
· 截止时刻:<strong>{{ meta.cutoff_end }}</strong>
|
||||||
<template v-if="meta.channel_name">
|
<template v-if="meta.channel_name">
|
||||||
· 渠道:<strong>{{ meta.channel_name }}</strong>
|
· 渠道:<strong>{{ meta.channel_name }}</strong>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="meta.settlement_month">
|
||||||
|
· 处方池:<strong>{{
|
||||||
|
meta.require_system_auto_prescription ? '仅系统代开' : '含手动与系统'
|
||||||
|
}}</strong>
|
||||||
|
<template v-if="meta.explicit_order_time_window">
|
||||||
|
· 列表对齐:<strong>是</strong></template>
|
||||||
|
</template>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</el-alert>
|
</el-alert>
|
||||||
@@ -136,27 +164,43 @@
|
|||||||
<div class="cs-reconcile">
|
<div class="cs-reconcile">
|
||||||
<div class="cs-reconcile__head">
|
<div class="cs-reconcile__head">
|
||||||
<div class="cs-reconcile__tags">
|
<div class="cs-reconcile__tags">
|
||||||
<el-tag v-if="confirmInfo?.status === 1" type="success" effect="dark">已确定业绩</el-tag>
|
<el-tag v-if="settlementConfirmedLocked" type="success" effect="dark">已确定业绩</el-tag>
|
||||||
<el-tag v-else type="warning" effect="plain">待确定业绩</el-tag>
|
<el-tag v-else-if="confirmInfo?.status === 2" type="warning" effect="plain">已撤回确定</el-tag>
|
||||||
|
<el-tag v-else type="info" effect="plain">待确定业绩</el-tag>
|
||||||
<span
|
<span
|
||||||
v-if="confirmInfo?.status === 1 && confirmInfo.confirmed_at_text"
|
v-if="settlementConfirmedLocked && confirmInfo?.confirmed_at_text"
|
||||||
class="cs-reconcile__meta"
|
class="cs-reconcile__meta"
|
||||||
>
|
>
|
||||||
{{ confirmInfo.confirmed_admin_name || '—' }} · {{ confirmInfo.confirmed_at_text }}
|
{{ confirmInfo.confirmed_admin_name || '—' }} · {{ confirmInfo.confirmed_at_text }}
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
v-else-if="confirmInfo?.status === 2 && confirmInfo.revoked_at_text"
|
||||||
|
class="cs-reconcile__meta"
|
||||||
|
>
|
||||||
|
撤回:{{ confirmInfo.revoked_admin_name || '—' }} · {{ confirmInfo.revoked_at_text }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="cs-reconcile__toolbar">
|
<div class="cs-reconcile__toolbar">
|
||||||
<el-button type="primary" plain @click="openReconcileDialog">业绩明细核对</el-button>
|
<el-button type="primary" plain @click="openReconcileDialog">业绩明细核对</el-button>
|
||||||
<el-button :disabled="confirmInfo?.status === 1 || reconcileSaving" @click="handleSaveReconcile">
|
<el-button :disabled="settlementConfirmedLocked || reconcileSaving" @click="handleSaveReconcile">
|
||||||
保存核对备注
|
保存核对备注
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="warning"
|
type="warning"
|
||||||
:disabled="confirmInfo?.status === 1 || confirmSubmitting"
|
:disabled="settlementConfirmedLocked || confirmSubmitting"
|
||||||
@click="handleConfirmFinalize"
|
@click="handleConfirmFinalize"
|
||||||
>
|
>
|
||||||
确定本期业绩
|
确定本期业绩
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="settlementConfirmedLocked"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:loading="confirmRevoking"
|
||||||
|
@click="handleConfirmRevoke"
|
||||||
|
>
|
||||||
|
撤回确定
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-input
|
<el-input
|
||||||
@@ -166,7 +210,7 @@
|
|||||||
maxlength="500"
|
maxlength="500"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
placeholder="核对备注(可与财务线下对齐后填写;确定业绩前可反复保存)"
|
placeholder="核对备注(可与财务线下对齐后填写;确定业绩前可反复保存)"
|
||||||
:disabled="confirmInfo?.status === 1"
|
:disabled="settlementConfirmedLocked"
|
||||||
class="cs-reconcile__note"
|
class="cs-reconcile__note"
|
||||||
/>
|
/>
|
||||||
<p v-if="Number(totalAgg.carry_in_order_count) > 0" class="cs-reconcile__carry">
|
<p v-if="Number(totalAgg.carry_in_order_count) > 0" class="cs-reconcile__carry">
|
||||||
@@ -245,6 +289,8 @@
|
|||||||
按诊单业绩归属医助 <strong>×</strong> 挂号渠道(doctor_appointment.channels)拆分到行;
|
按诊单业绩归属医助 <strong>×</strong> 挂号渠道(doctor_appointment.channels)拆分到行;
|
||||||
<strong>「复诊2」「复诊3」「自媒体1」等保持各自独立</strong>,未关联挂号统一归到「未匹配挂号渠道」。
|
<strong>「复诊2」「复诊3」「自媒体1」等保持各自独立</strong>,未关联挂号统一归到「未匹配挂号渠道」。
|
||||||
同一医助多行<strong>自动合并「医助 / 归属中心」单元格</strong>,「顺延汇入」为上期结转进入本期订单池的单数。
|
同一医助多行<strong>自动合并「医助 / 归属中心」单元格</strong>,「顺延汇入」为上期结转进入本期订单池的单数。
|
||||||
|
<strong>展示部门</strong>有勾选时,本表仅列出<strong>可归入该部门上下文</strong>的业绩医助(与人事部门落在所选部门范围内的账号一致)。
|
||||||
|
<strong>点击挂号渠道</strong>:查看该行对应的业务订单明细(与上方部门筛选一致)。
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<el-table
|
<el-table
|
||||||
@@ -264,9 +310,10 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag
|
<el-tag
|
||||||
size="small"
|
size="small"
|
||||||
class="cs-channel-tag"
|
class="cs-channel-tag cs-channel-tag--clickable"
|
||||||
:class="channelTagClass(row.appt_channel_name)"
|
:class="channelTagClass(row.appt_channel_name)"
|
||||||
effect="plain"
|
effect="plain"
|
||||||
|
@click.stop="openOrdersForAssistantChannel(row)"
|
||||||
>
|
>
|
||||||
{{ row.appt_channel_name }}
|
{{ row.appt_channel_name }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
@@ -293,6 +340,8 @@
|
|||||||
<span class="cs-table-head__hint">
|
<span class="cs-table-head__hint">
|
||||||
按处方创建账号(开方人) <strong>×</strong> 挂号渠道拆分;
|
按处方创建账号(开方人) <strong>×</strong> 挂号渠道拆分;
|
||||||
可与医助维度交叉核对;同一医生多行自动合并「医生 / 归属中心」单元格。
|
可与医助维度交叉核对;同一医生多行自动合并「医生 / 归属中心」单元格。
|
||||||
|
<strong>展示部门</strong>有勾选时,与医助表相同,仅统计<strong>该部门上下文内</strong>订单对应的开方医生。
|
||||||
|
<strong>点击挂号渠道</strong>:查看该行对应的业务订单明细。
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<el-table
|
<el-table
|
||||||
@@ -312,9 +361,10 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag
|
<el-tag
|
||||||
size="small"
|
size="small"
|
||||||
class="cs-channel-tag"
|
class="cs-channel-tag cs-channel-tag--clickable"
|
||||||
:class="channelTagClass(row.appt_channel_name)"
|
:class="channelTagClass(row.appt_channel_name)"
|
||||||
effect="plain"
|
effect="plain"
|
||||||
|
@click.stop="openOrdersForDoctorChannel(row)"
|
||||||
>
|
>
|
||||||
{{ row.appt_channel_name }}
|
{{ row.appt_channel_name }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
@@ -341,7 +391,7 @@
|
|||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="reconcileDialogVisible"
|
v-model="reconcileDialogVisible"
|
||||||
title="业绩明细核对"
|
title="业绩明细核对"
|
||||||
width="1040px"
|
width="1280px"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
class="cs-dialog"
|
class="cs-dialog"
|
||||||
@open="onReconcileDialogOpen"
|
@open="onReconcileDialogOpen"
|
||||||
@@ -354,7 +404,26 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<el-table v-loading="linesLoading" :data="linesRows" border stripe size="small" max-height="440">
|
<el-table v-loading="linesLoading" :data="linesRows" border stripe size="small" max-height="440">
|
||||||
<el-table-column prop="order_no" label="业务订单号" min-width="116" show-overflow-tooltip />
|
<el-table-column prop="order_no" label="业务订单号" min-width="128" show-overflow-tooltip />
|
||||||
|
<el-table-column label="快递单号" min-width="132" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.tracking_number || '—' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="挂号日期" min-width="124" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span>{{ row.appointment_date_text || '—' }}</span>
|
||||||
|
<el-tag
|
||||||
|
v-if="row.appointment_status && Number(row.appointment_status) !== 3"
|
||||||
|
class="cs-tag-appt-muted"
|
||||||
|
type="info"
|
||||||
|
size="small"
|
||||||
|
effect="plain"
|
||||||
|
>未完结挂号</el-tag
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="挂号渠道" min-width="124" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.appt_channel_name || '—' }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="assistant_name" label="业绩医助" min-width="100" show-overflow-tooltip />
|
<el-table-column prop="assistant_name" label="业绩医助" min-width="100" show-overflow-tooltip />
|
||||||
<el-table-column prop="doctor_name" label="开方医生" min-width="100" show-overflow-tooltip />
|
<el-table-column prop="doctor_name" label="开方医生" min-width="100" show-overflow-tooltip />
|
||||||
<el-table-column prop="primary_dept_name" label="归属部门" min-width="100" show-overflow-tooltip />
|
<el-table-column prop="primary_dept_name" label="归属部门" min-width="100" show-overflow-tooltip />
|
||||||
@@ -373,8 +442,20 @@
|
|||||||
<span v-else class="cs-muted">—</span>
|
<span v-else class="cs-muted">—</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="sign_time_text" label="签收时间" min-width="152" show-overflow-tooltip />
|
<el-table-column label="顺延说明" min-width="200" show-overflow-tooltip>
|
||||||
<el-table-column prop="pay_time_text" label="尾款支付" min-width="152" show-overflow-tooltip />
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.bucket === 'deferred' && row.bucket_defer_text" class="cs-muted">{{
|
||||||
|
row.bucket_defer_text
|
||||||
|
}}</span>
|
||||||
|
<span v-else class="cs-muted">—</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="物流签收" min-width="152" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.sign_time_text || '—' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="pay_time_text" label="尾款支付" min-width="152" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.pay_time_text || '—' }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="create_time_text" label="订单创建" width="152" />
|
<el-table-column prop="create_time_text" label="订单创建" width="152" />
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="cs-dialog__pager">
|
<div class="cs-dialog__pager">
|
||||||
@@ -390,8 +471,8 @@
|
|||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="leafOrdersVisible"
|
v-model="leafOrdersVisible"
|
||||||
:title="leafOrdersDept ? `已完成业绩订单 · ${leafOrdersDept.name}` : '已完成业绩订单'"
|
:title="leafOrdersDialogTitle"
|
||||||
width="1040px"
|
width="1280px"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
class="cs-dialog"
|
class="cs-dialog"
|
||||||
>
|
>
|
||||||
@@ -403,12 +484,33 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
<span class="cs-dialog__hint">
|
<span class="cs-dialog__hint">
|
||||||
结算月:<strong>{{ meta.settlement_month || settlementMonth }}</strong>
|
结算月:<strong>{{ meta.settlement_month || settlementMonth }}</strong>
|
||||||
<template v-if="meta.order_month"> · 订单创建月:<strong>{{ meta.order_month }}</strong></template>
|
<template v-if="orderWindowLabel && orderWindowLabel !== '—'"
|
||||||
|
> · 订单创建:<strong>{{ orderWindowLabel }}</strong></template
|
||||||
|
>
|
||||||
<template v-if="meta.channel_name"> · 渠道:<strong>{{ meta.channel_name }}</strong></template>
|
<template v-if="meta.channel_name"> · 渠道:<strong>{{ meta.channel_name }}</strong></template>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<el-table v-loading="leafOrdersLoading" :data="leafOrdersRows" border stripe size="small" max-height="440">
|
<el-table v-loading="leafOrdersLoading" :data="leafOrdersRows" border stripe size="small" max-height="440">
|
||||||
<el-table-column prop="order_no" label="业务订单号" min-width="116" show-overflow-tooltip />
|
<el-table-column prop="order_no" label="业务订单号" min-width="128" show-overflow-tooltip />
|
||||||
|
<el-table-column label="快递单号" min-width="132" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.tracking_number || '—' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="挂号日期" min-width="124" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span>{{ row.appointment_date_text || '—' }}</span>
|
||||||
|
<el-tag
|
||||||
|
v-if="row.appointment_status && Number(row.appointment_status) !== 3"
|
||||||
|
class="cs-tag-appt-muted"
|
||||||
|
type="info"
|
||||||
|
size="small"
|
||||||
|
effect="plain"
|
||||||
|
>未完结挂号</el-tag
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="挂号渠道" min-width="124" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.appt_channel_name || '—' }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="assistant_name" label="业绩医助" min-width="100" show-overflow-tooltip />
|
<el-table-column prop="assistant_name" label="业绩医助" min-width="100" show-overflow-tooltip />
|
||||||
<el-table-column prop="doctor_name" label="开方医生" min-width="100" show-overflow-tooltip />
|
<el-table-column prop="doctor_name" label="开方医生" min-width="100" show-overflow-tooltip />
|
||||||
<el-table-column prop="primary_dept_name" label="归属部门" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="primary_dept_name" label="归属部门" min-width="120" show-overflow-tooltip />
|
||||||
@@ -427,8 +529,20 @@
|
|||||||
<span v-else class="cs-muted">—</span>
|
<span v-else class="cs-muted">—</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="sign_time_text" label="签收时间" min-width="152" show-overflow-tooltip />
|
<el-table-column label="顺延说明" min-width="200" show-overflow-tooltip>
|
||||||
<el-table-column prop="pay_time_text" label="尾款支付" min-width="152" show-overflow-tooltip />
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.bucket === 'deferred' && row.bucket_defer_text" class="cs-muted">{{
|
||||||
|
row.bucket_defer_text
|
||||||
|
}}</span>
|
||||||
|
<span v-else class="cs-muted">—</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="物流签收" min-width="152" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.sign_time_text || '—' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="pay_time_text" label="尾款支付" min-width="152" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.pay_time_text || '—' }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="create_time_text" label="订单创建" width="152" />
|
<el-table-column prop="create_time_text" label="订单创建" width="152" />
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="cs-dialog__pager">
|
<div class="cs-dialog__pager">
|
||||||
@@ -445,12 +559,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, reactive, ref } from 'vue'
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import { ArrowRight, ArrowUp, RefreshRight, Search, Tickets } from '@element-plus/icons-vue'
|
import { ArrowRight, ArrowUp, RefreshRight, Search, Tickets } from '@element-plus/icons-vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import {
|
import {
|
||||||
commissionSettlementChannelOptions,
|
commissionSettlementChannelOptions,
|
||||||
commissionSettlementConfirmFinalize,
|
commissionSettlementConfirmFinalize,
|
||||||
|
commissionSettlementConfirmRevoke,
|
||||||
commissionSettlementDeptOptions,
|
commissionSettlementDeptOptions,
|
||||||
commissionSettlementOrderLines,
|
commissionSettlementOrderLines,
|
||||||
commissionSettlementOverview,
|
commissionSettlementOverview,
|
||||||
@@ -565,6 +680,16 @@ interface CsDoctorRow {
|
|||||||
deferred_period_amount: number
|
deferred_period_amount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LeafOrderDrillDept = { mode: 'dept'; dept_id: number; dept_name: string }
|
||||||
|
type LeafOrderDrillChannel = {
|
||||||
|
mode: 'channel'
|
||||||
|
title: string
|
||||||
|
assistant_id?: number
|
||||||
|
doctor_id?: number
|
||||||
|
appt_channel_value: number
|
||||||
|
}
|
||||||
|
type LeafOrderDrill = LeafOrderDrillDept | LeafOrderDrillChannel
|
||||||
|
|
||||||
interface CsAssistChannelRow extends CsAssistRow {
|
interface CsAssistChannelRow extends CsAssistRow {
|
||||||
appt_channel_value: number
|
appt_channel_value: number
|
||||||
appt_channel_name: string
|
appt_channel_name: string
|
||||||
@@ -584,7 +709,27 @@ function defaultSettlementMonth(): string {
|
|||||||
return `${y}-${m}`
|
return `${y}-${m}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pad2(n: number): string {
|
||||||
|
return String(n).padStart(2, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateTime(d: Date): string {
|
||||||
|
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 订单池默认:结算月「上一自然月」整段(与未传 start/end 的后端行为一致) */
|
||||||
|
function defaultOrderTimeRangeForSettlementMonth(sm: string): [string, string] {
|
||||||
|
const m = /^(\d{4})-(\d{2})$/.exec(sm)
|
||||||
|
const y = m ? parseInt(m[1], 10) : new Date().getFullYear()
|
||||||
|
const monthNum = m ? parseInt(m[2], 10) : new Date().getMonth() + 1
|
||||||
|
const start = new Date(y, monthNum - 2, 1, 0, 0, 0)
|
||||||
|
const end = new Date(y, monthNum - 1, 0, 23, 59, 59)
|
||||||
|
return [formatDateTime(start), formatDateTime(end)]
|
||||||
|
}
|
||||||
|
|
||||||
const settlementMonth = ref<string>(defaultSettlementMonth())
|
const settlementMonth = ref<string>(defaultSettlementMonth())
|
||||||
|
const orderTimeRange = ref<[string, string] | null>(defaultOrderTimeRangeForSettlementMonth(settlementMonth.value))
|
||||||
|
const requireSystemAutoOnly = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const deptOptions = ref<DeptOption[]>([])
|
const deptOptions = ref<DeptOption[]>([])
|
||||||
const deptOptionsLoading = ref(false)
|
const deptOptionsLoading = ref(false)
|
||||||
@@ -612,9 +757,14 @@ const totalAgg = ref<{
|
|||||||
const meta = reactive({
|
const meta = reactive({
|
||||||
settlement_month: '',
|
settlement_month: '',
|
||||||
order_month: '',
|
order_month: '',
|
||||||
|
order_start_time: '',
|
||||||
|
order_end_time: '',
|
||||||
|
fulfillment_status: 3,
|
||||||
|
explicit_order_time_window: false,
|
||||||
cutoff_end: '',
|
cutoff_end: '',
|
||||||
rule_note: '',
|
rule_note: '',
|
||||||
channel_name: '',
|
channel_name: '',
|
||||||
|
require_system_auto_prescription: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const confirmInfo = ref<{
|
const confirmInfo = ref<{
|
||||||
@@ -622,10 +772,17 @@ const confirmInfo = ref<{
|
|||||||
reconcile_note?: string
|
reconcile_note?: string
|
||||||
confirmed_admin_name?: string
|
confirmed_admin_name?: string
|
||||||
confirmed_at_text?: string
|
confirmed_at_text?: string
|
||||||
|
revoked_admin_name?: string
|
||||||
|
revoked_at_text?: string
|
||||||
|
is_confirmed_locked?: boolean
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
const reconcileNote = ref('')
|
const reconcileNote = ref('')
|
||||||
const reconcileSaving = ref(false)
|
const reconcileSaving = ref(false)
|
||||||
const confirmSubmitting = ref(false)
|
const confirmSubmitting = ref(false)
|
||||||
|
const confirmRevoking = ref(false)
|
||||||
|
|
||||||
|
/** 仅 status=1 时锁定,禁止保存备注 / 重复确定;status=2 已撤回可再操作 */
|
||||||
|
const settlementConfirmedLocked = computed(() => Number(confirmInfo.value?.status) === 1)
|
||||||
|
|
||||||
const reconcileDialogVisible = ref(false)
|
const reconcileDialogVisible = ref(false)
|
||||||
const linesLoading = ref(false)
|
const linesLoading = ref(false)
|
||||||
@@ -636,7 +793,7 @@ const linesPage = ref(1)
|
|||||||
const linesPageSize = ref(20)
|
const linesPageSize = ref(20)
|
||||||
|
|
||||||
const leafOrdersVisible = ref(false)
|
const leafOrdersVisible = ref(false)
|
||||||
const leafOrdersDept = ref<{ id: number; name: string } | null>(null)
|
const leafOrderDrill = ref<LeafOrderDrill | null>(null)
|
||||||
const leafOrdersBucket = ref<string>('all')
|
const leafOrdersBucket = ref<string>('all')
|
||||||
const leafOrdersRows = ref<Record<string, any>[]>([])
|
const leafOrdersRows = ref<Record<string, any>[]>([])
|
||||||
const leafOrdersTotal = ref(0)
|
const leafOrdersTotal = ref(0)
|
||||||
@@ -644,6 +801,20 @@ const leafOrdersPage = ref(1)
|
|||||||
const leafOrdersPageSize = ref(20)
|
const leafOrdersPageSize = ref(20)
|
||||||
const leafOrdersLoading = ref(false)
|
const leafOrdersLoading = ref(false)
|
||||||
|
|
||||||
|
const leafOrdersDialogTitle = computed(() => {
|
||||||
|
const d = leafOrderDrill.value
|
||||||
|
if (!d) return '已完成业绩订单'
|
||||||
|
if (d.mode === 'dept') return `已完成业绩订单 · ${d.dept_name}`
|
||||||
|
return d.title
|
||||||
|
})
|
||||||
|
|
||||||
|
const orderWindowLabel = computed(() => {
|
||||||
|
if (meta.order_start_time && meta.order_end_time) {
|
||||||
|
return `${meta.order_start_time}~${meta.order_end_time}`
|
||||||
|
}
|
||||||
|
return meta.order_month || '—'
|
||||||
|
})
|
||||||
|
|
||||||
const deptChildrenMap = computed(() => {
|
const deptChildrenMap = computed(() => {
|
||||||
const map = new Map<number, number[]>()
|
const map = new Map<number, number[]>()
|
||||||
for (const d of deptOptions.value) {
|
for (const d of deptOptions.value) {
|
||||||
@@ -718,7 +889,45 @@ function openOrderLinesForDept(deptId: number, deptName: string) {
|
|||||||
ElMessage.warning('请选择结算月')
|
ElMessage.warning('请选择结算月')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
leafOrdersDept.value = { id: deptId, name: deptName }
|
leafOrderDrill.value = { mode: 'dept', dept_id: deptId, dept_name: deptName }
|
||||||
|
leafOrdersBucket.value = 'all'
|
||||||
|
leafOrdersPage.value = 1
|
||||||
|
leafOrdersRows.value = []
|
||||||
|
leafOrdersTotal.value = 0
|
||||||
|
leafOrdersVisible.value = true
|
||||||
|
loadLeafOrders(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openOrdersForAssistantChannel(row: CsAssistChannelRow) {
|
||||||
|
if (!settlementMonth.value) {
|
||||||
|
ElMessage.warning('请选择结算月')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
leafOrderDrill.value = {
|
||||||
|
mode: 'channel',
|
||||||
|
title: `业务订单 · ${row.appt_channel_name} · 医助 ${row.assistant_name}`,
|
||||||
|
assistant_id: row.assistant_id,
|
||||||
|
appt_channel_value: Number(row.appt_channel_value ?? 0),
|
||||||
|
}
|
||||||
|
leafOrdersBucket.value = 'all'
|
||||||
|
leafOrdersPage.value = 1
|
||||||
|
leafOrdersRows.value = []
|
||||||
|
leafOrdersTotal.value = 0
|
||||||
|
leafOrdersVisible.value = true
|
||||||
|
loadLeafOrders(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openOrdersForDoctorChannel(row: CsDoctorChannelRow) {
|
||||||
|
if (!settlementMonth.value) {
|
||||||
|
ElMessage.warning('请选择结算月')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
leafOrderDrill.value = {
|
||||||
|
mode: 'channel',
|
||||||
|
title: `业务订单 · ${row.appt_channel_name} · 医生 ${row.doctor_name}`,
|
||||||
|
doctor_id: row.doctor_id,
|
||||||
|
appt_channel_value: Number(row.appt_channel_value ?? 0),
|
||||||
|
}
|
||||||
leafOrdersBucket.value = 'all'
|
leafOrdersBucket.value = 'all'
|
||||||
leafOrdersPage.value = 1
|
leafOrdersPage.value = 1
|
||||||
leafOrdersRows.value = []
|
leafOrdersRows.value = []
|
||||||
@@ -728,18 +937,30 @@ function openOrderLinesForDept(deptId: number, deptName: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadLeafOrders(page: number) {
|
async function loadLeafOrders(page: number) {
|
||||||
if (!settlementMonth.value || !leafOrdersDept.value) return
|
if (!settlementMonth.value || !leafOrderDrill.value) return
|
||||||
leafOrdersLoading.value = true
|
leafOrdersLoading.value = true
|
||||||
leafOrdersPage.value = page
|
leafOrdersPage.value = page
|
||||||
try {
|
try {
|
||||||
|
const d = leafOrderDrill.value
|
||||||
const p: Record<string, any> = {
|
const p: Record<string, any> = {
|
||||||
settlement_month: settlementMonth.value,
|
settlement_month: settlementMonth.value,
|
||||||
dept_ids: String(leafOrdersDept.value.id),
|
|
||||||
page,
|
page,
|
||||||
page_size: leafOrdersPageSize.value,
|
page_size: leafOrdersPageSize.value,
|
||||||
}
|
}
|
||||||
if (selectedChannel.value) {
|
if (d.mode === 'dept') {
|
||||||
p.channel_code = selectedChannel.value
|
p.dept_ids = String(d.dept_id)
|
||||||
|
if (selectedChannel.value) {
|
||||||
|
p.channel_code = selectedChannel.value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Object.assign(p, buildQueryParams())
|
||||||
|
p.appt_channel_value = d.appt_channel_value
|
||||||
|
if (d.assistant_id != null) {
|
||||||
|
p.assistant_id = d.assistant_id
|
||||||
|
}
|
||||||
|
if (d.doctor_id != null) {
|
||||||
|
p.doctor_id = d.doctor_id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (leafOrdersBucket.value && leafOrdersBucket.value !== 'all') {
|
if (leafOrdersBucket.value && leafOrdersBucket.value !== 'all') {
|
||||||
p.bucket = leafOrdersBucket.value
|
p.bucket = leafOrdersBucket.value
|
||||||
@@ -796,6 +1017,11 @@ async function loadOverview() {
|
|||||||
ElMessage.warning('请选择结算月')
|
ElMessage.warning('请选择结算月')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const ot = orderTimeRange.value
|
||||||
|
if (!ot || !ot[0] || !ot[1]) {
|
||||||
|
ElMessage.warning('请设置订单创建时间范围')
|
||||||
|
return
|
||||||
|
}
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res: any = await commissionSettlementOverview(buildQueryParams() as any)
|
const res: any = await commissionSettlementOverview(buildQueryParams() as any)
|
||||||
@@ -807,9 +1033,21 @@ async function loadOverview() {
|
|||||||
totalAgg.value = res?.total || {}
|
totalAgg.value = res?.total || {}
|
||||||
meta.settlement_month = res?.settlement_month || sm
|
meta.settlement_month = res?.settlement_month || sm
|
||||||
meta.order_month = res?.order_month || ''
|
meta.order_month = res?.order_month || ''
|
||||||
|
meta.order_start_time = res?.order_start_time ? String(res.order_start_time) : ''
|
||||||
|
meta.order_end_time = res?.order_end_time ? String(res.order_end_time) : ''
|
||||||
|
meta.fulfillment_status =
|
||||||
|
res?.fulfillment_status != null ? Number(res.fulfillment_status) : 3
|
||||||
|
meta.explicit_order_time_window =
|
||||||
|
typeof res?.explicit_order_time_window === 'boolean'
|
||||||
|
? res.explicit_order_time_window
|
||||||
|
: !!(meta.order_start_time && meta.order_end_time)
|
||||||
meta.cutoff_end = res?.cutoff_end || ''
|
meta.cutoff_end = res?.cutoff_end || ''
|
||||||
meta.rule_note = res?.rule_note || ''
|
meta.rule_note = res?.rule_note || ''
|
||||||
meta.channel_name = res?.channel_name || ''
|
meta.channel_name = res?.channel_name || ''
|
||||||
|
meta.require_system_auto_prescription =
|
||||||
|
typeof res?.require_system_auto_prescription === 'boolean'
|
||||||
|
? res.require_system_auto_prescription
|
||||||
|
: false
|
||||||
confirmInfo.value = res?.confirm ?? null
|
confirmInfo.value = res?.confirm ?? null
|
||||||
reconcileNote.value =
|
reconcileNote.value =
|
||||||
res?.confirm?.reconcile_note != null ? String(res.confirm.reconcile_note) : ''
|
res?.confirm?.reconcile_note != null ? String(res.confirm.reconcile_note) : ''
|
||||||
@@ -829,7 +1067,18 @@ async function loadOverview() {
|
|||||||
|
|
||||||
function buildQueryParams(): Record<string, any> {
|
function buildQueryParams(): Record<string, any> {
|
||||||
const sm = settlementMonth.value
|
const sm = settlementMonth.value
|
||||||
const p: Record<string, any> = { settlement_month: sm }
|
const p: Record<string, any> = {
|
||||||
|
settlement_month: sm,
|
||||||
|
fulfillment_status: 3,
|
||||||
|
}
|
||||||
|
const ot = orderTimeRange.value
|
||||||
|
if (ot && ot[0] && ot[1]) {
|
||||||
|
p.start_time = ot[0]
|
||||||
|
p.end_time = ot[1]
|
||||||
|
}
|
||||||
|
if (requireSystemAutoOnly.value) {
|
||||||
|
p.require_system_auto_prescription = 1
|
||||||
|
}
|
||||||
if (selectedDeptIds.value.length > 0) {
|
if (selectedDeptIds.value.length > 0) {
|
||||||
p.dept_ids = selectedDeptIds.value.join(',')
|
p.dept_ids = selectedDeptIds.value.join(',')
|
||||||
}
|
}
|
||||||
@@ -882,8 +1131,8 @@ async function handleSaveReconcile() {
|
|||||||
ElMessage.warning('请选择结算月')
|
ElMessage.warning('请选择结算月')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (confirmInfo.value?.status === 1) {
|
if (settlementConfirmedLocked.value) {
|
||||||
ElMessage.warning('已确定业绩不可修改备注')
|
ElMessage.warning('已确定业绩不可修改备注;请先撤回确定')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
reconcileSaving.value = true
|
reconcileSaving.value = true
|
||||||
@@ -908,7 +1157,7 @@ async function handleConfirmFinalize() {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await ElMessageBox.confirm(
|
||||||
'确定后将为「顺延下期」订单写入结转记录;下一结算月在相同渠道与部门筛选下自动并入统计。已确定后不可在线撤销,请与财务核对后再操作。',
|
'确定后将为「顺延下期」订单写入结转记录,下一结算月在相同渠道与部门筛选下自动并入统计。确定后仍可「撤回确定」以清除结转并重新核对(与财务对齐后再操作)。',
|
||||||
'确定本期业绩',
|
'确定本期业绩',
|
||||||
{
|
{
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
@@ -936,8 +1185,40 @@ async function handleConfirmFinalize() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleConfirmRevoke() {
|
||||||
|
if (!settlementMonth.value) {
|
||||||
|
ElMessage.warning('请选择结算月')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
'将撤回本次「确定业绩」状态,并删除已写入的顺延结转记录(下期将不再汇入这些单)。确定后继续?',
|
||||||
|
'撤回确定业绩',
|
||||||
|
{
|
||||||
|
confirmButtonText: '撤回',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
confirmRevoking.value = true
|
||||||
|
try {
|
||||||
|
await commissionSettlementConfirmRevoke(buildQueryParams() as any)
|
||||||
|
ElMessage.success('已撤回确定;可重新保存备注并再次确定本期业绩')
|
||||||
|
await loadOverview()
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.msg || e?.message || '撤回失败')
|
||||||
|
} finally {
|
||||||
|
confirmRevoking.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
settlementMonth.value = defaultSettlementMonth()
|
settlementMonth.value = defaultSettlementMonth()
|
||||||
|
orderTimeRange.value = defaultOrderTimeRangeForSettlementMonth(settlementMonth.value)
|
||||||
|
requireSystemAutoOnly.value = false
|
||||||
selectedDeptIds.value = []
|
selectedDeptIds.value = []
|
||||||
selectedChannel.value = ''
|
selectedChannel.value = ''
|
||||||
loadOverview()
|
loadOverview()
|
||||||
@@ -1053,6 +1334,12 @@ function getSummaries(param: { columns: Array<{ property?: string }>; data: CsRo
|
|||||||
return sums
|
return sums
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(settlementMonth, (v) => {
|
||||||
|
if (v) {
|
||||||
|
orderTimeRange.value = defaultOrderTimeRangeForSettlementMonth(v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([loadDeptOptions(), loadChannelOptions()])
|
await Promise.all([loadDeptOptions(), loadChannelOptions()])
|
||||||
await loadOverview()
|
await loadOverview()
|
||||||
@@ -1249,7 +1536,24 @@ onMounted(async () => {
|
|||||||
flex: 1 1 280px;
|
flex: 1 1 280px;
|
||||||
min-width: 240px;
|
min-width: 240px;
|
||||||
}
|
}
|
||||||
|
.filter-field--rangewrap {
|
||||||
|
flex: 1 1 440px;
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
.filter-month { width: 168px; }
|
.filter-month { width: 168px; }
|
||||||
|
.filter-datetimerange {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 520px;
|
||||||
|
}
|
||||||
|
.filter-sa-check {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--cs-text);
|
||||||
|
}
|
||||||
|
.cs-tag-appt-muted {
|
||||||
|
margin-left: 6px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
.filter-select--channel { width: 220px; }
|
.filter-select--channel { width: 220px; }
|
||||||
.filter-dept-tree-select { width: 100%; min-width: 240px; }
|
.filter-dept-tree-select { width: 100%; min-width: 240px; }
|
||||||
.filter-actions {
|
.filter-actions {
|
||||||
@@ -1581,6 +1885,12 @@ onMounted(async () => {
|
|||||||
border-color: #cbd5e1 !important;
|
border-color: #cbd5e1 !important;
|
||||||
color: #64748b !important;
|
color: #64748b !important;
|
||||||
}
|
}
|
||||||
|
&--clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(0.96);
|
||||||
|
}
|
||||||
|
}
|
||||||
&--neutral {
|
&--neutral {
|
||||||
background: #f0fdfa !important;
|
background: #f0fdfa !important;
|
||||||
border-color: #99f6e4 !important;
|
border-color: #99f6e4 !important;
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)">
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,8 @@ use app\adminapi\logic\stats\YejiStatsLogic;
|
|||||||
|
|
||||||
* - POST stats.commissionSettlement/confirmFinalize
|
* - POST stats.commissionSettlement/confirmFinalize
|
||||||
|
|
||||||
|
* - POST stats.commissionSettlement/confirmRevoke
|
||||||
|
|
||||||
* - GET stats.commissionSettlement/deptOptions
|
* - GET stats.commissionSettlement/deptOptions
|
||||||
|
|
||||||
* - GET stats.commissionSettlement/channelOptions
|
* - GET stats.commissionSettlement/channelOptions
|
||||||
@@ -106,6 +108,16 @@ class CommissionSettlementController extends BaseAdminController
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function confirmRevoke()
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
return $this->data(YejiStatsLogic::commissionSettlementConfirmRevoke($this->request->post(), $this->adminId, $this->adminInfo));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function deptOptions()
|
public function deptOptions()
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
+21
@@ -48,6 +48,27 @@ class PrescriptionController extends BaseAdminController
|
|||||||
return $this->success('编辑成功');
|
return $this->success('编辑成功');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notes 修正处方患者姓名手机性别
|
||||||
|
*/
|
||||||
|
public function patchPatient()
|
||||||
|
{
|
||||||
|
$params = (new PrescriptionValidate())->post()->goCheck('patchPatient');
|
||||||
|
$ok = PrescriptionLogic::patchPatientContact(
|
||||||
|
(int) $params['id'],
|
||||||
|
(string) $params['patient_name'],
|
||||||
|
(string) $params['phone'],
|
||||||
|
(int) $params['gender'],
|
||||||
|
(int) $this->adminId,
|
||||||
|
$this->adminInfo
|
||||||
|
);
|
||||||
|
if (!$ok) {
|
||||||
|
return $this->fail(PrescriptionLogic::getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success('已更新');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @notes 删除处方
|
* @notes 删除处方
|
||||||
*/
|
*/
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
+41
@@ -20,6 +20,14 @@ class PrescriptionOrderController extends BaseAdminController
|
|||||||
return $this->dataLists(new PrescriptionOrderLists());
|
return $this->dataLists(new PrescriptionOrderLists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处方业务订单导出(与 lists 相同筛选条件,Excel;参数 export=1 预估 export=2 下载)
|
||||||
|
*/
|
||||||
|
public function export()
|
||||||
|
{
|
||||||
|
return $this->dataLists(new PrescriptionOrderLists());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 指定诊单下可关联的支付单(待支付/已支付,创建/编辑业务订单时多选关联)
|
* 指定诊单下可关联的支付单(待支付/已支付,创建/编辑业务订单时多选关联)
|
||||||
*/
|
*/
|
||||||
@@ -116,6 +124,25 @@ class PrescriptionOrderController extends BaseAdminController
|
|||||||
return $this->success('修改成功');
|
return $this->success('修改成功');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置发货类型(甘草 / 洛阳)
|
||||||
|
*/
|
||||||
|
public function setShipMode()
|
||||||
|
{
|
||||||
|
$params = (new PrescriptionOrderValidate())->post()->goCheck('setShipMode');
|
||||||
|
$result = PrescriptionOrderLogic::setShipMode(
|
||||||
|
(int) $params['id'],
|
||||||
|
(string) ($params['ship_mode'] ?? 'gancao'),
|
||||||
|
$this->adminId,
|
||||||
|
$this->adminInfo
|
||||||
|
);
|
||||||
|
if ($result === false) {
|
||||||
|
return $this->fail(PrescriptionOrderLogic::getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success('已保存', $result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改关联处方的患者姓名与手机号(订单详情场景)
|
* 修改关联处方的患者姓名与手机号(订单详情场景)
|
||||||
*/
|
*/
|
||||||
@@ -285,6 +312,20 @@ class PrescriptionOrderController extends BaseAdminController
|
|||||||
return $this->success('关联支付单成功', $result);
|
return $this->success('关联支付单成功', $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已发货/已签收:仅提交完单申请(不新增/关联支付单),并重置支付审核为待审核
|
||||||
|
*/
|
||||||
|
public function requestCompletion()
|
||||||
|
{
|
||||||
|
$params = (new PrescriptionOrderValidate())->post()->goCheck('requestCompletion');
|
||||||
|
$result = PrescriptionOrderLogic::requestCompletion($params, $this->adminId, $this->adminInfo);
|
||||||
|
if ($result === false) {
|
||||||
|
return $this->fail(PrescriptionOrderLogic::getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success('完单申请已提交', $result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将「已发货」且支付审核已通过的订单标记为「已完成」
|
* 将「已发货」且支付审核已通过的订单标记为「已完成」
|
||||||
*/
|
*/
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user