Merge branch '20260604-long'
This commit is contained in:
@@ -26,10 +26,6 @@ bin-release/
|
|||||||
/.cursor
|
/.cursor
|
||||||
/.codex
|
/.codex
|
||||||
/.agents
|
/.agents
|
||||||
/.claude
|
|
||||||
/.claude
|
|
||||||
/.claude
|
|
||||||
/.kbctx
|
|
||||||
/server/.spool
|
/server/.spool
|
||||||
/server/.claude
|
/server/.claude
|
||||||
/.spool
|
/.spool
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<template v-if="variant === 'split'">
|
||||||
|
<el-form-item :label="label" :class="{ 'is-compact': compact }">
|
||||||
|
<el-radio-group v-model="timeType" :size="compact ? 'small' : 'default'" @change="emit('time-type-change')">
|
||||||
|
<el-radio-button v-if="showAll" label="all">全部</el-radio-button>
|
||||||
|
<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">本月</el-radio-button>
|
||||||
|
<el-radio-button label="custom">自定义</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="timeType === 'custom'">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRangeModel"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
:size="compact ? 'small' : 'default'"
|
||||||
|
:style="{ width: pickerWidth }"
|
||||||
|
@change="emit('custom-change')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
<el-form-item v-else :label="label" :class="{ 'is-compact': compact }">
|
||||||
|
<div class="list-time-filter-inline">
|
||||||
|
<el-radio-group v-model="timeType" :size="compact ? 'small' : 'default'" @change="emit('time-type-change')">
|
||||||
|
<el-radio-button v-if="showAll" label="all">全部</el-radio-button>
|
||||||
|
<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">本月</el-radio-button>
|
||||||
|
<el-radio-button label="custom">自定义</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-date-picker
|
||||||
|
v-if="timeType === 'custom'"
|
||||||
|
v-model="dateRangeModel"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
:size="compact ? 'small' : 'default'"
|
||||||
|
:style="{ width: pickerWidth }"
|
||||||
|
@change="emit('custom-change')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ListTimeType } from '@/hooks/useListTimeFilter'
|
||||||
|
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
label?: string
|
||||||
|
/** split:与自录统计一致,两个 form-item;inline:处方订单筛选用单行 */
|
||||||
|
variant?: 'split' | 'inline'
|
||||||
|
pickerWidth?: string
|
||||||
|
/** 紧凑模式:small 尺寸 radio / 日期 */
|
||||||
|
compact?: boolean
|
||||||
|
/** 是否显示「全部」(不限时间)选项 */
|
||||||
|
showAll?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
label: '时间范围',
|
||||||
|
variant: 'split',
|
||||||
|
pickerWidth: '260px',
|
||||||
|
compact: false,
|
||||||
|
showAll: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const timeType = defineModel<ListTimeType>('timeType', { required: true })
|
||||||
|
const dateRangeModel = defineModel<string[]>('dateRange', { default: () => [] })
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'time-type-change': []
|
||||||
|
'custom-change': []
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.list-time-filter-inline {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.is-compact.el-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -88,13 +88,17 @@
|
|||||||
共 <strong>{{ formData.herbs.length }}</strong> 味(主方 {{ mainHerbCount }} / 辅方
|
共 <strong>{{ formData.herbs.length }}</strong> 味(主方 {{ mainHerbCount }} / 辅方
|
||||||
{{ auxHerbCount }})· 可手动添加、从处方库或粘贴药方批量录入
|
{{ auxHerbCount }})· 可手动添加、从处方库或粘贴药方批量录入
|
||||||
</p>
|
</p>
|
||||||
|
<p v-if="herbsLocked" class="rp-toolbar__lock-tip">
|
||||||
|
<el-tag type="danger" size="small" effect="plain">禁用修改</el-tag>
|
||||||
|
含「禁用修改」处方库模板,药材已锁定,不可增删改,如需调整请重新「从处方库导入」覆盖
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rp-toolbar__actions" role="toolbar" aria-label="处方药材快捷操作">
|
<div class="rp-toolbar__actions" role="toolbar" aria-label="处方药材快捷操作">
|
||||||
<el-button type="primary" size="small" :icon="CirclePlus" @click="handleAddHerb('主方')">
|
<el-button type="primary" size="small" :icon="CirclePlus" :disabled="herbsLocked" @click="handleAddHerb('主方')">
|
||||||
添加主方
|
添加主方
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" plain size="small" @click="handleAddHerb('辅方')">
|
<el-button type="primary" plain size="small" :disabled="herbsLocked" @click="handleAddHerb('辅方')">
|
||||||
添加辅方
|
添加辅方
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -135,7 +139,7 @@
|
|||||||
:class="{ 'herb-editor-card--dup': isDuplicateHerbName(row.index) }"
|
:class="{ 'herb-editor-card--dup': isDuplicateHerbName(row.index) }"
|
||||||
>
|
>
|
||||||
<div class="herb-editor-card__title">主方 {{ row.index + 1 }}</div>
|
<div class="herb-editor-card__title">主方 {{ row.index + 1 }}</div>
|
||||||
<MedicineNameSelect v-model="formData.herbs[row.index].name" class="herb-editor-card__select" />
|
<MedicineNameSelect v-model="formData.herbs[row.index].name" :disabled="herbsLocked" class="herb-editor-card__select" />
|
||||||
<div
|
<div
|
||||||
v-if="isDuplicateHerbName(row.index)"
|
v-if="isDuplicateHerbName(row.index)"
|
||||||
class="herb-editor-card__dup-hint text-amber-600 text-xs mb-1"
|
class="herb-editor-card__dup-hint text-amber-600 text-xs mb-1"
|
||||||
@@ -148,11 +152,12 @@
|
|||||||
:min="0.1"
|
:min="0.1"
|
||||||
:step="1"
|
:step="1"
|
||||||
:precision="1"
|
:precision="1"
|
||||||
|
:disabled="herbsLocked"
|
||||||
placeholder="剂量(克)"
|
placeholder="剂量(克)"
|
||||||
class="herb-editor-card__dose"
|
class="herb-editor-card__dose"
|
||||||
/>
|
/>
|
||||||
<span class="text-gray-500 shrink-0">克</span>
|
<span class="text-gray-500 shrink-0">克</span>
|
||||||
<el-button type="danger" size="small" link @click="handleRemoveHerb(row.index)">
|
<el-button v-if="!herbsLocked" type="danger" size="small" link @click="handleRemoveHerb(row.index)">
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,7 +176,7 @@
|
|||||||
:class="{ 'herb-editor-card--dup': isDuplicateHerbName(row.index) }"
|
:class="{ 'herb-editor-card--dup': isDuplicateHerbName(row.index) }"
|
||||||
>
|
>
|
||||||
<div class="herb-editor-card__title">辅方</div>
|
<div class="herb-editor-card__title">辅方</div>
|
||||||
<MedicineNameSelect v-model="formData.herbs[row.index].name" class="herb-editor-card__select" />
|
<MedicineNameSelect v-model="formData.herbs[row.index].name" :disabled="herbsLocked" class="herb-editor-card__select" />
|
||||||
<div
|
<div
|
||||||
v-if="isDuplicateHerbName(row.index)"
|
v-if="isDuplicateHerbName(row.index)"
|
||||||
class="herb-editor-card__dup-hint text-amber-600 text-xs mb-1"
|
class="herb-editor-card__dup-hint text-amber-600 text-xs mb-1"
|
||||||
@@ -184,11 +189,12 @@
|
|||||||
:min="0.1"
|
:min="0.1"
|
||||||
:step="1"
|
:step="1"
|
||||||
:precision="1"
|
:precision="1"
|
||||||
|
:disabled="herbsLocked"
|
||||||
placeholder="剂量(克)"
|
placeholder="剂量(克)"
|
||||||
class="herb-editor-card__dose"
|
class="herb-editor-card__dose"
|
||||||
/>
|
/>
|
||||||
<span class="text-gray-500 shrink-0">克</span>
|
<span class="text-gray-500 shrink-0">克</span>
|
||||||
<el-button type="danger" size="small" link @click="handleRemoveHerb(row.index)">
|
<el-button v-if="!herbsLocked" type="danger" size="small" link @click="handleRemoveHerb(row.index)">
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1028,6 +1034,8 @@ interface Herb {
|
|||||||
name: string
|
name: string
|
||||||
dosage: number
|
dosage: number
|
||||||
formula_type?: FormulaType
|
formula_type?: FormulaType
|
||||||
|
/** 来自「禁用修改」处方库模板:锁定后不可增删改,只能再次导入覆盖 */
|
||||||
|
locked?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuxUsageForm = {
|
type AuxUsageForm = {
|
||||||
@@ -1044,11 +1052,15 @@ function normalizeFormulaType(v: unknown): FormulaType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizeHerbRow(raw: any): Herb {
|
function normalizeHerbRow(raw: any): Herb {
|
||||||
return {
|
const row: Herb = {
|
||||||
name: String(raw?.name ?? '').trim(),
|
name: String(raw?.name ?? '').trim(),
|
||||||
dosage: Number(raw?.dosage) || 0,
|
dosage: Number(raw?.dosage) || 0,
|
||||||
formula_type: normalizeFormulaType(raw?.formula_type)
|
formula_type: normalizeFormulaType(raw?.formula_type)
|
||||||
}
|
}
|
||||||
|
if (raw?.locked === true || raw?.locked === 1) {
|
||||||
|
row.locked = true
|
||||||
|
}
|
||||||
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultAuxUsage(prescriptionType = '浓缩水丸'): AuxUsageForm {
|
function defaultAuxUsage(prescriptionType = '浓缩水丸'): AuxUsageForm {
|
||||||
@@ -1430,6 +1442,12 @@ const auxHerbRows = computed(() =>
|
|||||||
const mainHerbCount = computed(() => mainHerbRows.value.length)
|
const mainHerbCount = computed(() => mainHerbRows.value.length)
|
||||||
const auxHerbCount = computed(() => auxHerbRows.value.length)
|
const auxHerbCount = computed(() => auxHerbRows.value.length)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 整张处方是否被锁定:只要任一药材来自「禁用修改」模板即锁定。
|
||||||
|
* 锁定后:不可增删改任意药材,仅可再次「从处方库导入」覆盖。
|
||||||
|
*/
|
||||||
|
const herbsLocked = computed(() => formData.herbs.some((h) => (h as Herb).locked === true))
|
||||||
|
|
||||||
const libraryDoctorId = computed(() => {
|
const libraryDoctorId = computed(() => {
|
||||||
const uid = Number(userStore.userInfo?.id)
|
const uid = Number(userStore.userInfo?.id)
|
||||||
return uid > 0 ? uid : 0
|
return uid > 0 ? uid : 0
|
||||||
@@ -2029,10 +2047,18 @@ function findDuplicateHerbNames(): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAddHerb = (formulaType: FormulaType = '主方') => {
|
const handleAddHerb = (formulaType: FormulaType = '主方') => {
|
||||||
|
if (herbsLocked.value) {
|
||||||
|
feedback.msgWarning('该处方含「禁用修改」模板,药材已锁定,请重新从处方库导入覆盖')
|
||||||
|
return
|
||||||
|
}
|
||||||
formData.herbs.push({ name: '', dosage: 6, formula_type: formulaType })
|
formData.herbs.push({ name: '', dosage: 6, formula_type: formulaType })
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRemoveHerb = (index: number) => {
|
const handleRemoveHerb = (index: number) => {
|
||||||
|
if (herbsLocked.value) {
|
||||||
|
feedback.msgWarning('该处方含「禁用修改」模板,药材已锁定,不可删除')
|
||||||
|
return
|
||||||
|
}
|
||||||
formData.herbs.splice(index, 1)
|
formData.herbs.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2089,10 +2115,18 @@ const handleImportLibrary = (row: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formulaType = normalizeFormulaType(row.formula_type)
|
const formulaType = normalizeFormulaType(row.formula_type)
|
||||||
const imported = (JSON.parse(JSON.stringify(row.herbs)) as any[]).map((h) => ({
|
// 「禁用修改」模板:导入的药材打上锁定标记 → 整张处方锁定(不可增删改,只能再次导入覆盖)
|
||||||
...normalizeHerbRow(h),
|
const isLockedTemplate = Number(row?.disable_edit) === 1
|
||||||
formula_type: formulaType
|
const imported = (JSON.parse(JSON.stringify(row.herbs)) as any[]).map((h) => {
|
||||||
}))
|
const herb = normalizeHerbRow(h)
|
||||||
|
herb.formula_type = formulaType
|
||||||
|
if (isLockedTemplate) {
|
||||||
|
herb.locked = true
|
||||||
|
} else {
|
||||||
|
delete herb.locked
|
||||||
|
}
|
||||||
|
return herb
|
||||||
|
})
|
||||||
if (libraryImportMode.value === 'replace') {
|
if (libraryImportMode.value === 'replace') {
|
||||||
const kept = formData.herbs.filter((h) => normalizeFormulaType(h.formula_type) !== formulaType)
|
const kept = formData.herbs.filter((h) => normalizeFormulaType(h.formula_type) !== formulaType)
|
||||||
formData.herbs = [...kept, ...imported]
|
formData.herbs = [...kept, ...imported]
|
||||||
@@ -2532,6 +2566,17 @@ defineExpose({
|
|||||||
color: var(--el-text-color-regular);
|
color: var(--el-text-color-regular);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rp-toolbar__lock-tip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 6px 0 0;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.55;
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
.rp-toolbar__actions {
|
.rp-toolbar__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
export type ListTimeType = 'all' | 'today' | 'yesterday' | 'week' | 'month' | 'custom'
|
||||||
|
|
||||||
|
export function formatYmd(d: Date): string {
|
||||||
|
const y = d.getFullYear()
|
||||||
|
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(d.getDate()).padStart(2, '0')
|
||||||
|
return `${y}-${m}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 列表时间筛选:month 为本自然月 1 日~月末 */
|
||||||
|
export function resolveListTimeRange(timeType: ListTimeType, dateRange: string[]): [string, string] {
|
||||||
|
const today = formatYmd(new Date())
|
||||||
|
|
||||||
|
switch (timeType) {
|
||||||
|
case 'all':
|
||||||
|
return ['', '']
|
||||||
|
case 'yesterday': {
|
||||||
|
const d = new Date()
|
||||||
|
d.setDate(d.getDate() - 1)
|
||||||
|
const y = formatYmd(d)
|
||||||
|
return [y, y]
|
||||||
|
}
|
||||||
|
case 'week': {
|
||||||
|
const end = new Date()
|
||||||
|
const start = new Date()
|
||||||
|
start.setDate(start.getDate() - 6)
|
||||||
|
return [formatYmd(start), formatYmd(end)]
|
||||||
|
}
|
||||||
|
case 'month': {
|
||||||
|
const now = new Date()
|
||||||
|
const start = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||||
|
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
||||||
|
return [formatYmd(start), formatYmd(end)]
|
||||||
|
}
|
||||||
|
case 'custom': {
|
||||||
|
let start = dateRange[0] || today
|
||||||
|
let end = dateRange[1] || today
|
||||||
|
if (start > end) {
|
||||||
|
;[start, end] = [end, start]
|
||||||
|
}
|
||||||
|
return [start, end]
|
||||||
|
}
|
||||||
|
case 'today':
|
||||||
|
default:
|
||||||
|
return [today, today]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useListTimeFilter(defaultType: ListTimeType = 'today') {
|
||||||
|
const time_type = ref<ListTimeType>(defaultType)
|
||||||
|
const dateRange = ref<string[]>([])
|
||||||
|
|
||||||
|
const resolve = () => resolveListTimeRange(time_type.value, dateRange.value)
|
||||||
|
|
||||||
|
const handleTimeTypeChange = (onQuery: () => void) => {
|
||||||
|
if (time_type.value !== 'custom') {
|
||||||
|
dateRange.value = []
|
||||||
|
onQuery()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCustomDateChange = (onQuery: () => void) => {
|
||||||
|
if (dateRange.value.length === 2) {
|
||||||
|
onQuery()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetTimeFilter = () => {
|
||||||
|
time_type.value = defaultType
|
||||||
|
dateRange.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyCustomRange = (start: string, end: string) => {
|
||||||
|
if (!start || !end) return
|
||||||
|
time_type.value = 'custom'
|
||||||
|
dateRange.value = [start, end]
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
time_type,
|
||||||
|
dateRange,
|
||||||
|
resolve,
|
||||||
|
handleTimeTypeChange,
|
||||||
|
handleCustomDateChange,
|
||||||
|
resetTimeFilter,
|
||||||
|
applyCustomRange,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -679,17 +679,21 @@
|
|||||||
粘贴文本解析药名与剂量;须与药品库名称完全一致才录入(导入至主方)
|
粘贴文本解析药名与剂量;须与药品库名称完全一致才录入(导入至主方)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="herbsLocked" class="flex items-center gap-2 flex-wrap mt-2 text-xs text-[var(--el-color-danger)]">
|
||||||
|
<el-tag type="danger" size="small" effect="plain">禁用修改</el-tag>
|
||||||
|
含「禁用修改」处方库模板,药材已锁定,不可增删改,如需调整请重新「从处方库导入」覆盖
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="herb-formula-block mt-3">
|
<div class="herb-formula-block mt-3">
|
||||||
<div class="herb-formula-block__head">
|
<div class="herb-formula-block__head">
|
||||||
<el-tag type="primary" size="small">主方</el-tag>
|
<el-tag type="primary" size="small">主方</el-tag>
|
||||||
<el-button type="primary" size="small" @click="addHerb('主方')">添加主方药材</el-button>
|
<el-button type="primary" size="small" :disabled="herbsLocked" @click="addHerb('主方')">添加主方药材</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="mainHerbRows" class="mt-2" border empty-text="暂无主方药材">
|
<el-table :data="mainHerbRows" class="mt-2" border empty-text="暂无主方药材">
|
||||||
<el-table-column label="序号" type="index" width="60" />
|
<el-table-column label="序号" type="index" width="60" />
|
||||||
<el-table-column label="药材名称" min-width="220">
|
<el-table-column label="药材名称" min-width="220">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<MedicineNameSelect v-model="editForm.herbs[row.index].name" class="w-full" />
|
<MedicineNameSelect v-model="editForm.herbs[row.index].name" :disabled="herbsLocked" class="w-full" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="剂量(克)" min-width="120">
|
<el-table-column label="剂量(克)" min-width="120">
|
||||||
@@ -699,6 +703,7 @@
|
|||||||
:min="0"
|
:min="0"
|
||||||
:precision="1"
|
:precision="1"
|
||||||
:step="0.5"
|
:step="0.5"
|
||||||
|
:disabled="herbsLocked"
|
||||||
placeholder="剂量"
|
placeholder="剂量"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
@@ -706,7 +711,8 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="80">
|
<el-table-column label="操作" width="80">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="danger" link @click="removeHerb(row.index)">删除</el-button>
|
<el-button v-if="!herbsLocked" type="danger" link @click="removeHerb(row.index)">删除</el-button>
|
||||||
|
<span v-else class="text-gray-400 text-xs">已锁定</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -715,7 +721,7 @@
|
|||||||
<div class="herb-formula-block mt-4">
|
<div class="herb-formula-block mt-4">
|
||||||
<div class="herb-formula-block__head">
|
<div class="herb-formula-block__head">
|
||||||
<el-tag type="warning" size="small">辅方</el-tag>
|
<el-tag type="warning" size="small">辅方</el-tag>
|
||||||
<el-button type="primary" size="small" plain @click="addHerb('辅方')">
|
<el-button type="primary" size="small" plain :disabled="herbsLocked" @click="addHerb('辅方')">
|
||||||
添加辅方药材
|
添加辅方药材
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -723,7 +729,7 @@
|
|||||||
<el-table-column label="序号" type="index" width="60" />
|
<el-table-column label="序号" type="index" width="60" />
|
||||||
<el-table-column label="药材名称" min-width="220">
|
<el-table-column label="药材名称" min-width="220">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<MedicineNameSelect v-model="editForm.herbs[row.index].name" class="w-full" />
|
<MedicineNameSelect v-model="editForm.herbs[row.index].name" :disabled="herbsLocked" class="w-full" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="剂量(克)" min-width="120">
|
<el-table-column label="剂量(克)" min-width="120">
|
||||||
@@ -733,6 +739,7 @@
|
|||||||
:min="0"
|
:min="0"
|
||||||
:precision="1"
|
:precision="1"
|
||||||
:step="0.5"
|
:step="0.5"
|
||||||
|
:disabled="herbsLocked"
|
||||||
placeholder="剂量"
|
placeholder="剂量"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
@@ -740,7 +747,8 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="80">
|
<el-table-column label="操作" width="80">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="danger" link @click="removeHerb(row.index)">删除</el-button>
|
<el-button v-if="!herbsLocked" type="danger" link @click="removeHerb(row.index)">删除</el-button>
|
||||||
|
<span v-else class="text-gray-400 text-xs">已锁定</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -1698,7 +1706,7 @@ import jsPDF from 'jspdf'
|
|||||||
const TcmDiagnosisEditView = defineAsyncComponent(() => import('@/views/tcm/diagnosis/edit.vue'))
|
const TcmDiagnosisEditView = defineAsyncComponent(() => import('@/views/tcm/diagnosis/edit.vue'))
|
||||||
|
|
||||||
type FormulaType = '主方' | '辅方'
|
type FormulaType = '主方' | '辅方'
|
||||||
type HerbRow = { name: string; dosage: number; formula_type: FormulaType }
|
type HerbRow = { name: string; dosage: number; formula_type: FormulaType; locked?: boolean }
|
||||||
|
|
||||||
type AuxUsageForm = {
|
type AuxUsageForm = {
|
||||||
dosage_amount?: number
|
dosage_amount?: number
|
||||||
@@ -1798,11 +1806,15 @@ function normalizeFormulaType(v: unknown): FormulaType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizeHerbRow(raw: any): HerbRow {
|
function normalizeHerbRow(raw: any): HerbRow {
|
||||||
return {
|
const row: HerbRow = {
|
||||||
name: String(raw?.name ?? '').trim(),
|
name: String(raw?.name ?? '').trim(),
|
||||||
dosage: Number(raw?.dosage) || 0,
|
dosage: Number(raw?.dosage) || 0,
|
||||||
formula_type: normalizeFormulaType(raw?.formula_type)
|
formula_type: normalizeFormulaType(raw?.formula_type)
|
||||||
}
|
}
|
||||||
|
if (raw?.locked === true || raw?.locked === 1) {
|
||||||
|
row.locked = true
|
||||||
|
}
|
||||||
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSlipHerbs(raw: unknown): HerbRow[] {
|
function normalizeSlipHerbs(raw: unknown): HerbRow[] {
|
||||||
@@ -2481,6 +2493,12 @@ const auxHerbRows = computed(() =>
|
|||||||
.filter(({ herb }) => normalizeFormulaType(herb.formula_type) === '辅方')
|
.filter(({ herb }) => normalizeFormulaType(herb.formula_type) === '辅方')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 整张处方是否被锁定:只要任一药材来自「禁用修改」处方库模板即锁定。
|
||||||
|
* 锁定后不可增删改任意药材,只能再次「从处方库导入」覆盖。
|
||||||
|
*/
|
||||||
|
const herbsLocked = computed(() => editForm.herbs.some((h) => (h as HerbRow).locked === true))
|
||||||
|
|
||||||
const slipDietaryText = computed(() => {
|
const slipDietaryText = computed(() => {
|
||||||
const d = slipView.value?.dietary_taboo
|
const d = slipView.value?.dietary_taboo
|
||||||
if (Array.isArray(d)) return d.filter(Boolean).join('、')
|
if (Array.isArray(d)) return d.filter(Boolean).join('、')
|
||||||
@@ -3441,10 +3459,18 @@ const handleImportLibrary = (row: any) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const formulaType = normalizeFormulaType(row.formula_type)
|
const formulaType = normalizeFormulaType(row.formula_type)
|
||||||
const imported = (JSON.parse(JSON.stringify(row.herbs)) as any[]).map((h) => ({
|
// 「禁用修改」模板:导入药材打上锁定标记 → 整张处方锁定(不可增删改,只能再次导入覆盖)
|
||||||
...normalizeHerbRow(h),
|
const isLockedTemplate = Number(row?.disable_edit) === 1
|
||||||
formula_type: formulaType
|
const imported = (JSON.parse(JSON.stringify(row.herbs)) as any[]).map((h) => {
|
||||||
}))
|
const herb = normalizeHerbRow(h)
|
||||||
|
herb.formula_type = formulaType
|
||||||
|
if (isLockedTemplate) {
|
||||||
|
herb.locked = true
|
||||||
|
} else {
|
||||||
|
delete herb.locked
|
||||||
|
}
|
||||||
|
return herb
|
||||||
|
})
|
||||||
if (libraryImportMode.value === 'replace') {
|
if (libraryImportMode.value === 'replace') {
|
||||||
const kept = editForm.herbs.filter((h) => normalizeFormulaType(h.formula_type) !== formulaType)
|
const kept = editForm.herbs.filter((h) => normalizeFormulaType(h.formula_type) !== formulaType)
|
||||||
editForm.herbs = [...kept, ...imported]
|
editForm.herbs = [...kept, ...imported]
|
||||||
@@ -3650,6 +3676,10 @@ async function handlePasteRecipeImport() {
|
|||||||
|
|
||||||
// 添加药材(主方 / 辅方)
|
// 添加药材(主方 / 辅方)
|
||||||
const addHerb = (formulaType: FormulaType = '主方') => {
|
const addHerb = (formulaType: FormulaType = '主方') => {
|
||||||
|
if (herbsLocked.value) {
|
||||||
|
feedback.msgWarning('该处方含「禁用修改」模板,药材已锁定,请重新从处方库导入覆盖')
|
||||||
|
return
|
||||||
|
}
|
||||||
editForm.herbs.push({
|
editForm.herbs.push({
|
||||||
name: '',
|
name: '',
|
||||||
dosage: 0,
|
dosage: 0,
|
||||||
@@ -3659,6 +3689,10 @@ const addHerb = (formulaType: FormulaType = '主方') => {
|
|||||||
|
|
||||||
// 删除药材
|
// 删除药材
|
||||||
const removeHerb = (index: number) => {
|
const removeHerb = (index: number) => {
|
||||||
|
if (herbsLocked.value) {
|
||||||
|
feedback.msgWarning('该处方含「禁用修改」模板,药材已锁定,不可删除')
|
||||||
|
return
|
||||||
|
}
|
||||||
editForm.herbs.splice(index, 1)
|
editForm.herbs.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,13 @@
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="禁用修改" min-width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.disable_edit ? 'danger' : 'info'">
|
||||||
|
{{ row.disable_edit ? '已禁用' : '可修改' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="创建人" prop="creator_name" min-width="100" />
|
<el-table-column label="创建人" prop="creator_name" min-width="100" />
|
||||||
<el-table-column label="创建时间" prop="create_time" min-width="160" />
|
<el-table-column label="创建时间" prop="create_time" min-width="160" />
|
||||||
<el-table-column label="操作" width="180" fixed="right">
|
<el-table-column label="操作" width="180" fixed="right">
|
||||||
@@ -193,6 +200,21 @@
|
|||||||
勾选后,所有医生都可以查看和使用此处方模板
|
勾选后,所有医生都可以查看和使用此处方模板
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="禁用修改" prop="disable_edit">
|
||||||
|
<div class="flex flex-col gap-1 leading-none">
|
||||||
|
<el-switch
|
||||||
|
v-model="editForm.disable_edit"
|
||||||
|
:active-value="1"
|
||||||
|
:inactive-value="0"
|
||||||
|
active-text="开启"
|
||||||
|
inactive-text="关闭"
|
||||||
|
/>
|
||||||
|
<div class="text-xs text-gray-400 leading-relaxed">
|
||||||
|
开启后,导入此模板到处方时将锁定整张处方的药材,不可增删改,只能再次导入覆盖
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<template #footer v-if="editMode !== 'view'">
|
<template #footer v-if="editMode !== 'view'">
|
||||||
@@ -236,7 +258,8 @@ const editForm = reactive({
|
|||||||
prescription_name: '',
|
prescription_name: '',
|
||||||
formula_type: '主方',
|
formula_type: '主方',
|
||||||
herbs: [] as Array<{ name: string; dosage: number }>,
|
herbs: [] as Array<{ name: string; dosage: number }>,
|
||||||
is_public: 0
|
is_public: 0,
|
||||||
|
disable_edit: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
@@ -293,6 +316,7 @@ const resetForm = () => {
|
|||||||
editForm.formula_type = '主方'
|
editForm.formula_type = '主方'
|
||||||
editForm.herbs = []
|
editForm.herbs = []
|
||||||
editForm.is_public = 0
|
editForm.is_public = 0
|
||||||
|
editForm.disable_edit = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增处方
|
// 新增处方
|
||||||
@@ -310,6 +334,7 @@ const handleView = (row: any) => {
|
|||||||
editForm.formula_type = row.formula_type || '主方'
|
editForm.formula_type = row.formula_type || '主方'
|
||||||
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
||||||
editForm.is_public = row.is_public ?? 0
|
editForm.is_public = row.is_public ?? 0
|
||||||
|
editForm.disable_edit = row.disable_edit ?? 0
|
||||||
showEdit.value = true
|
showEdit.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,6 +346,7 @@ const handleEdit = (row: any) => {
|
|||||||
editForm.formula_type = row.formula_type || '主方'
|
editForm.formula_type = row.formula_type || '主方'
|
||||||
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
||||||
editForm.is_public = row.is_public ?? 0
|
editForm.is_public = row.is_public ?? 0
|
||||||
|
editForm.disable_edit = row.disable_edit ?? 0
|
||||||
showEdit.value = true
|
showEdit.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -173,17 +173,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-form class="ls-form" :model="queryParams" inline>
|
<el-form class="ls-form" :model="queryParams" inline>
|
||||||
<el-form-item class="w-[360px] min-w-[240px] max-w-full" label="创建时间">
|
<list-time-filter
|
||||||
<DaterangePicker
|
v-model:time-type="time_type"
|
||||||
v-model:startTime="queryParams.start_time"
|
v-model:date-range="dateRange"
|
||||||
v-model:endTime="queryParams.end_time"
|
label="创建时间"
|
||||||
picker-type="daterange"
|
variant="inline"
|
||||||
value-format="YYYY-MM-DD"
|
show-all
|
||||||
start-placeholder="开始日期"
|
picker-width="230px"
|
||||||
end-placeholder="结束日期"
|
@time-type-change="onTimeTypeChange"
|
||||||
class="!w-full"
|
@custom-change="onCustomDateChange"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item class="w-[260px]" label="订单号">
|
<el-form-item class="w-[260px]" label="订单号">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.order_no"
|
v-model="queryParams.order_no"
|
||||||
@@ -797,8 +796,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 顶部数据概览卡片 -->
|
<!-- 顶部数据概览卡片 -->
|
||||||
<el-row :gutter="16" class="mb-5">
|
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4 mb-5">
|
||||||
<el-col :xs="12" :sm="6">
|
|
||||||
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex items-center justify-between mb-1">
|
||||||
<div class="stat-title text-gray-500 text-xs">总金额</div>
|
<div class="stat-title text-gray-500 text-xs">总金额</div>
|
||||||
@@ -815,14 +813,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="stat-value text-red-500 font-bold text-xl">¥{{ detailData.amount }}</div>
|
<div class="stat-value text-red-500 font-bold text-xl">¥{{ detailData.amount }}</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
|
||||||
<el-col :xs="12" :sm="6">
|
|
||||||
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<div class="stat-title text-gray-500 text-xs mb-1">已付总额</div>
|
<div class="stat-title text-gray-500 text-xs mb-1">已付总额</div>
|
||||||
<div class="stat-value text-green-600 font-bold text-xl">¥{{ detailData.linked_pay_paid_total || 0 }}</div>
|
<div class="stat-value text-green-600 font-bold text-xl">¥{{ detailData.linked_pay_paid_total || 0 }}</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<el-col :xs="12" :sm="6">
|
<div class="stat-title text-gray-500 text-xs mb-1">退款金额</div>
|
||||||
|
<div
|
||||||
|
class="stat-value font-bold text-xl"
|
||||||
|
:class="Number(detailData.refund_amount) > 0 ? 'text-rose-500' : 'text-gray-400'"
|
||||||
|
>
|
||||||
|
¥{{ formatMoney(detailData.refund_amount || 0) }}
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<div class="stat-title text-gray-500 text-xs mb-1">需代收</div>
|
<div class="stat-title text-gray-500 text-xs mb-1">需代收</div>
|
||||||
<div
|
<div
|
||||||
@@ -832,14 +835,11 @@
|
|||||||
¥{{ formatMoney(detailAgencyToCollect) }}
|
¥{{ formatMoney(detailAgencyToCollect) }}
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
|
||||||
<el-col :xs="12" :sm="6">
|
|
||||||
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<div class="stat-title text-gray-500 text-xs mb-1">已付笔数</div>
|
<div class="stat-title text-gray-500 text-xs mb-1">已付笔数</div>
|
||||||
<div class="stat-value text-primary font-bold text-xl">{{ detailLinkedPayOrders.length }} 笔</div>
|
<div class="stat-value text-primary font-bold text-xl">{{ detailLinkedPayOrders.length }} 笔</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<!-- 左侧:处方单 -->
|
<!-- 左侧:处方单 -->
|
||||||
@@ -2856,7 +2856,8 @@
|
|||||||
import { computed, onMounted, reactive, ref, nextTick, watch, defineAsyncComponent } from 'vue'
|
import { computed, onMounted, reactive, ref, nextTick, watch, defineAsyncComponent } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { Refresh, Loading, ArrowDown, InfoFilled, QuestionFilled, Search, Calendar, Document, Link as LinkIcon, Wallet } from '@element-plus/icons-vue'
|
import { Refresh, Loading, ArrowDown, InfoFilled, QuestionFilled, Search, Calendar, Document, Link as LinkIcon, Wallet } from '@element-plus/icons-vue'
|
||||||
import DaterangePicker from '@/components/daterange-picker/index.vue'
|
import ListTimeFilter from '@/components/list-time-filter/index.vue'
|
||||||
|
import { useListTimeFilter } from '@/hooks/useListTimeFilter'
|
||||||
import {
|
import {
|
||||||
prescriptionOrderAuditPayment,
|
prescriptionOrderAuditPayment,
|
||||||
prescriptionOrderAuditPrescription,
|
prescriptionOrderAuditPrescription,
|
||||||
@@ -3162,9 +3163,6 @@ function handleExpressCompanyTabClick(value: '' | 'sf' | 'jd') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
/** 创建时间区间(与列表筛选一致;不影响「今日」统计) */
|
|
||||||
start_time: '' as string,
|
|
||||||
end_time: '' as string,
|
|
||||||
order_no: '',
|
order_no: '',
|
||||||
prescription_id: '' as string | number,
|
prescription_id: '' as string | number,
|
||||||
/** 诊单患者姓名/手机号(后台关联 diagnosis) */
|
/** 诊单患者姓名/手机号(后台关联 diagnosis) */
|
||||||
@@ -3252,6 +3250,24 @@ function handleFocusBoardSearch(key: 'pendingRx' | 'pendingPay' | 'pendingShip'
|
|||||||
resetPage()
|
resetPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
time_type,
|
||||||
|
dateRange,
|
||||||
|
resolve: resolveCreateTimeRange,
|
||||||
|
handleTimeTypeChange,
|
||||||
|
handleCustomDateChange,
|
||||||
|
resetTimeFilter,
|
||||||
|
applyCustomRange,
|
||||||
|
} = useListTimeFilter('all')
|
||||||
|
|
||||||
|
const handleQuery = () => {
|
||||||
|
pager.page = 1
|
||||||
|
getLists()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTimeTypeChange = () => handleTimeTypeChange(handleQuery)
|
||||||
|
const onCustomDateChange = () => handleCustomDateChange(handleQuery)
|
||||||
|
|
||||||
function normalizePrescriptionOrderListQuery(params: Record<string, unknown>): 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) {
|
||||||
@@ -3331,11 +3347,15 @@ function normalizePrescriptionOrderListQuery(params: Record<string, unknown>): R
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchLists(params: Record<string, unknown>) {
|
async function fetchLists(params: Record<string, unknown>) {
|
||||||
const p = normalizePrescriptionOrderListQuery({ ...params })
|
const [start_time, end_time] = resolveCreateTimeRange()
|
||||||
|
const p = normalizePrescriptionOrderListQuery({ ...params, start_time, end_time })
|
||||||
return prescriptionOrderLists(p)
|
return prescriptionOrderLists(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
const prescriptionOrderExportParams = computed(() => normalizePrescriptionOrderListQuery({ ...queryParams }))
|
const prescriptionOrderExportParams = computed(() => {
|
||||||
|
const [start_time, end_time] = resolveCreateTimeRange()
|
||||||
|
return normalizePrescriptionOrderListQuery({ ...queryParams, start_time, end_time })
|
||||||
|
})
|
||||||
|
|
||||||
const { pager, getLists, resetPage, resetParams } = usePaging({
|
const { pager, getLists, resetPage, resetParams } = usePaging({
|
||||||
fetchFun: fetchLists,
|
fetchFun: fetchLists,
|
||||||
@@ -3370,8 +3390,9 @@ function applyRouteQueryToPrescriptionOrderList() {
|
|||||||
}
|
}
|
||||||
const st = normDate(q.start_time)
|
const st = normDate(q.start_time)
|
||||||
const et = normDate(q.end_time)
|
const et = normDate(q.end_time)
|
||||||
if (st) queryParams.start_time = st
|
if (st && et) {
|
||||||
if (et) queryParams.end_time = et
|
applyCustomRange(st, et)
|
||||||
|
}
|
||||||
const ad = q.assistant_dept_id
|
const ad = q.assistant_dept_id
|
||||||
if (ad !== undefined && ad !== null && String(ad) !== '') {
|
if (ad !== undefined && ad !== null && String(ad) !== '') {
|
||||||
const n = Number(ad)
|
const n = Number(ad)
|
||||||
@@ -3462,9 +3483,7 @@ const listStats = computed(() => {
|
|||||||
scopeHint =
|
scopeHint =
|
||||||
'统计口径:本人创建的订单 + 关联处方开方人为本人的订单(含医助代建);与下方筛选项(不含「创建时间」)一致'
|
'统计口径:本人创建的订单 + 关联处方开方人为本人的订单(含医助代建);与下方筛选项(不含「创建时间」)一致'
|
||||||
}
|
}
|
||||||
const hasCustomTime =
|
const headPrefix = time_type.value === 'all' ? '全部' : time_type.value === 'today' ? '今日' : '区间'
|
||||||
String(queryParams.start_time || '').trim() !== '' && String(queryParams.end_time || '').trim() !== ''
|
|
||||||
const headPrefix = hasCustomTime ? '区间' : '今日'
|
|
||||||
return {
|
return {
|
||||||
order: n(o),
|
order: n(o),
|
||||||
pay: n(pay),
|
pay: n(pay),
|
||||||
@@ -3536,8 +3555,7 @@ watch(
|
|||||||
|
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
activeFocusKey.value = ''
|
activeFocusKey.value = ''
|
||||||
queryParams.start_time = ''
|
resetTimeFilter()
|
||||||
queryParams.end_time = ''
|
|
||||||
queryParams.order_no = ''
|
queryParams.order_no = ''
|
||||||
queryParams.prescription_id = ''
|
queryParams.prescription_id = ''
|
||||||
queryParams.patient_keyword = ''
|
queryParams.patient_keyword = ''
|
||||||
|
|||||||
@@ -190,18 +190,17 @@
|
|||||||
<ArrowDown />
|
<ArrowDown />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<el-form v-show="!h5FilterCollapsed" class="ls-form" :model="queryParams" inline>
|
<el-form v-show="!h5FilterCollapsed" class="ls-form stats-filter-form" :model="queryParams" inline>
|
||||||
<el-form-item class="w-[360px] min-w-[240px] max-w-full" label="创建时间">
|
<list-time-filter
|
||||||
<DaterangePicker
|
v-model:time-type="time_type"
|
||||||
v-model:startTime="queryParams.start_time"
|
v-model:date-range="dateRange"
|
||||||
v-model:endTime="queryParams.end_time"
|
label="创建时间"
|
||||||
picker-type="daterange"
|
variant="inline"
|
||||||
value-format="YYYY-MM-DD"
|
show-all
|
||||||
start-placeholder="开始日期"
|
picker-width="260px"
|
||||||
end-placeholder="结束日期"
|
@time-type-change="onTimeTypeChange"
|
||||||
class="!w-full"
|
@custom-change="onCustomDateChange"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item class="w-[260px]" label="订单号">
|
<el-form-item class="w-[260px]" label="订单号">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.order_no"
|
v-model="queryParams.order_no"
|
||||||
@@ -703,8 +702,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 顶部数据概览卡片 -->
|
<!-- 顶部数据概览卡片 -->
|
||||||
<el-row :gutter="16" class="mb-5">
|
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4 mb-5">
|
||||||
<el-col :xs="12" :sm="6">
|
|
||||||
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex items-center justify-between mb-1">
|
||||||
<div class="stat-title text-gray-500 text-xs">总金额</div>
|
<div class="stat-title text-gray-500 text-xs">总金额</div>
|
||||||
@@ -721,14 +719,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="stat-value text-red-500 font-bold text-xl">¥{{ detailData.amount }}</div>
|
<div class="stat-value text-red-500 font-bold text-xl">¥{{ detailData.amount }}</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
|
||||||
<el-col :xs="12" :sm="6">
|
|
||||||
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<div class="stat-title text-gray-500 text-xs mb-1">已付总额</div>
|
<div class="stat-title text-gray-500 text-xs mb-1">已付总额</div>
|
||||||
<div class="stat-value text-green-600 font-bold text-xl">¥{{ detailData.linked_pay_paid_total || 0 }}</div>
|
<div class="stat-value text-green-600 font-bold text-xl">¥{{ detailData.linked_pay_paid_total || 0 }}</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<el-col :xs="12" :sm="6">
|
<div class="stat-title text-gray-500 text-xs mb-1">退款金额</div>
|
||||||
|
<div
|
||||||
|
class="stat-value font-bold text-xl"
|
||||||
|
:class="Number(detailData.refund_amount) > 0 ? 'text-rose-500' : 'text-gray-400'"
|
||||||
|
>
|
||||||
|
¥{{ formatMoney(detailData.refund_amount || 0) }}
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<div class="stat-title text-gray-500 text-xs mb-1">需代收</div>
|
<div class="stat-title text-gray-500 text-xs mb-1">需代收</div>
|
||||||
<div
|
<div
|
||||||
@@ -738,14 +741,11 @@
|
|||||||
¥{{ formatMoney(detailAgencyToCollect) }}
|
¥{{ formatMoney(detailAgencyToCollect) }}
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
|
||||||
<el-col :xs="12" :sm="6">
|
|
||||||
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
|
||||||
<div class="stat-title text-gray-500 text-xs mb-1">已付笔数</div>
|
<div class="stat-title text-gray-500 text-xs mb-1">已付笔数</div>
|
||||||
<div class="stat-value text-primary font-bold text-xl">{{ detailLinkedPayOrders.length }} 笔</div>
|
<div class="stat-value text-primary font-bold text-xl">{{ detailLinkedPayOrders.length }} 笔</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<!-- 左侧:处方单 -->
|
<!-- 左侧:处方单 -->
|
||||||
@@ -2507,7 +2507,8 @@ import {
|
|||||||
Wallet,
|
Wallet,
|
||||||
User
|
User
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import DaterangePicker from '@/components/daterange-picker/index.vue'
|
import ListTimeFilter from '@/components/list-time-filter/index.vue'
|
||||||
|
import { useListTimeFilter } from '@/hooks/useListTimeFilter'
|
||||||
import {
|
import {
|
||||||
prescriptionOrderAuditPayment,
|
prescriptionOrderAuditPayment,
|
||||||
prescriptionOrderAuditPrescription,
|
prescriptionOrderAuditPrescription,
|
||||||
@@ -2805,9 +2806,6 @@ function handleExpressCompanyTabClick(value: '' | 'sf' | 'jd') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
/** 创建时间区间(与列表筛选一致;不影响「今日」统计) */
|
|
||||||
start_time: '' as string,
|
|
||||||
end_time: '' as string,
|
|
||||||
order_no: '',
|
order_no: '',
|
||||||
prescription_id: '' as string | number,
|
prescription_id: '' as string | number,
|
||||||
/** 诊单患者姓名/手机号(后台关联 diagnosis) */
|
/** 诊单患者姓名/手机号(后台关联 diagnosis) */
|
||||||
@@ -2886,8 +2884,26 @@ function handleFocusBoardSearch(key: 'pendingRx' | 'pendingPay' | 'pendingShip'
|
|||||||
resetPage()
|
resetPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
time_type,
|
||||||
|
dateRange,
|
||||||
|
resolve: resolveCreateTimeRange,
|
||||||
|
handleTimeTypeChange,
|
||||||
|
handleCustomDateChange,
|
||||||
|
resetTimeFilter,
|
||||||
|
} = useListTimeFilter('all')
|
||||||
|
|
||||||
|
const handleQuery = () => {
|
||||||
|
pager.page = 1
|
||||||
|
getLists()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTimeTypeChange = () => handleTimeTypeChange(handleQuery)
|
||||||
|
const onCustomDateChange = () => handleCustomDateChange(handleQuery)
|
||||||
|
|
||||||
async function fetchLists(params: Record<string, unknown>) {
|
async function fetchLists(params: Record<string, unknown>) {
|
||||||
const p: Record<string, unknown> = { ...params }
|
const [start_time, end_time] = resolveCreateTimeRange()
|
||||||
|
const p: Record<string, unknown> = { ...params, start_time, end_time }
|
||||||
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 {
|
||||||
@@ -3041,9 +3057,7 @@ const listStats = computed(() => {
|
|||||||
scopeHint =
|
scopeHint =
|
||||||
'统计口径:本人创建的订单 + 关联处方开方人为本人的订单(含医助代建);与下方筛选项(不含「创建时间」)一致'
|
'统计口径:本人创建的订单 + 关联处方开方人为本人的订单(含医助代建);与下方筛选项(不含「创建时间」)一致'
|
||||||
}
|
}
|
||||||
const hasCustomTime =
|
const headPrefix = time_type.value === 'all' ? '全部' : time_type.value === 'today' ? '今日' : '区间'
|
||||||
String(queryParams.start_time || '').trim() !== '' && String(queryParams.end_time || '').trim() !== ''
|
|
||||||
const headPrefix = hasCustomTime ? '区间' : '今日'
|
|
||||||
return {
|
return {
|
||||||
order: n(o),
|
order: n(o),
|
||||||
pay: n(pay),
|
pay: n(pay),
|
||||||
@@ -3114,8 +3128,7 @@ watch(
|
|||||||
|
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
activeFocusKey.value = ''
|
activeFocusKey.value = ''
|
||||||
queryParams.start_time = ''
|
resetTimeFilter()
|
||||||
queryParams.end_time = ''
|
|
||||||
queryParams.order_no = ''
|
queryParams.order_no = ''
|
||||||
queryParams.prescription_id = ''
|
queryParams.prescription_id = ''
|
||||||
queryParams.patient_keyword = ''
|
queryParams.patient_keyword = ''
|
||||||
@@ -3381,11 +3394,11 @@ const h5FilterCollapsed = ref(true)
|
|||||||
const h5ActiveFilterCount = computed(() => {
|
const h5ActiveFilterCount = computed(() => {
|
||||||
const q = queryParams as any
|
const q = queryParams as any
|
||||||
const keys = [
|
const keys = [
|
||||||
'start_time', 'end_time', 'order_no', 'prescription_id',
|
'order_no', 'prescription_id',
|
||||||
'patient_keyword', 'express_keyword', 'doctor_id',
|
'patient_keyword', 'express_keyword', 'doctor_id',
|
||||||
'assistant_dept_id', 'assistant_id'
|
'assistant_dept_id', 'assistant_id'
|
||||||
]
|
]
|
||||||
let n = 0
|
let n = time_type.value !== 'all' ? 1 : 0
|
||||||
for (const k of keys) {
|
for (const k of keys) {
|
||||||
const v = q[k]
|
const v = q[k]
|
||||||
if (v === null || v === undefined) continue
|
if (v === null || v === undefined) continue
|
||||||
@@ -5520,6 +5533,12 @@ async function downloadPrescriptionSlipPdf() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.stats-filter-form {
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.prescription-order-page {
|
.prescription-order-page {
|
||||||
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
@@ -14,13 +14,13 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card class="!border-none" shadow="never">
|
<el-card class="!border-none" shadow="never">
|
||||||
<el-form class="mb-[-16px]" :model="queryParams" inline>
|
<el-form :inline="true" :model="queryParams" class="stats-filter-form">
|
||||||
<el-form-item label="日期范围">
|
<list-time-filter
|
||||||
<daterange-picker
|
v-model:time-type="time_type"
|
||||||
v-model:startTime="queryParams.start_date"
|
v-model:date-range="dateRange"
|
||||||
v-model:endTime="queryParams.end_date"
|
@time-type-change="onTimeTypeChange"
|
||||||
|
@custom-change="onCustomDateChange"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="自媒体渠道">
|
<el-form-item label="自媒体渠道">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="queryParams.media_channel_code"
|
v-model="queryParams.media_channel_code"
|
||||||
@@ -67,11 +67,11 @@
|
|||||||
v-model="queryParams.remark"
|
v-model="queryParams.remark"
|
||||||
placeholder="备注 / 创建人 / 修改人"
|
placeholder="备注 / 创建人 / 修改人"
|
||||||
clearable
|
clearable
|
||||||
@keyup.enter="resetPage"
|
@keyup.enter="handleQuery"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" @click="resetPage">查询</el-button>
|
<el-button type="primary" :loading="pager.loading" @click="handleQuery">查询</el-button>
|
||||||
<el-button @click="handleReset">重置</el-button>
|
<el-button @click="handleReset">重置</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@@ -148,7 +148,9 @@
|
|||||||
<script lang="ts" setup name="accountCostList">
|
<script lang="ts" setup name="accountCostList">
|
||||||
import { accountCostDelete, accountCostLists } from '@/api/finance'
|
import { accountCostDelete, accountCostLists } from '@/api/finance'
|
||||||
import { usePaging } from '@/hooks/usePaging'
|
import { usePaging } from '@/hooks/usePaging'
|
||||||
|
import { useListTimeFilter } from '@/hooks/useListTimeFilter'
|
||||||
import feedback from '@/utils/feedback'
|
import feedback from '@/utils/feedback'
|
||||||
|
import ListTimeFilter from '@/components/list-time-filter/index.vue'
|
||||||
|
|
||||||
import EditPopup from './edit.vue'
|
import EditPopup from './edit.vue'
|
||||||
|
|
||||||
@@ -172,18 +174,40 @@ const editRef = shallowRef<InstanceType<typeof EditPopup>>()
|
|||||||
const showEdit = ref(false)
|
const showEdit = ref(false)
|
||||||
|
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
start_date: '',
|
|
||||||
end_date: '',
|
|
||||||
media_channel_code: '',
|
media_channel_code: '',
|
||||||
dept_id: '',
|
dept_id: '',
|
||||||
remark: '',
|
remark: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { pager, getLists, resetPage } = usePaging({
|
const {
|
||||||
fetchFun: accountCostLists,
|
time_type,
|
||||||
|
dateRange,
|
||||||
|
resolve,
|
||||||
|
handleTimeTypeChange,
|
||||||
|
handleCustomDateChange,
|
||||||
|
resetTimeFilter,
|
||||||
|
} = useListTimeFilter('today')
|
||||||
|
|
||||||
|
const { pager, getLists } = usePaging({
|
||||||
|
fetchFun: async (params: Record<string, any>) => {
|
||||||
|
const [start_date, end_date] = resolve()
|
||||||
|
return accountCostLists({
|
||||||
|
...params,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
})
|
||||||
|
},
|
||||||
params: queryParams,
|
params: queryParams,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleQuery = () => {
|
||||||
|
pager.page = 1
|
||||||
|
getLists()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTimeTypeChange = () => handleTimeTypeChange(handleQuery)
|
||||||
|
const onCustomDateChange = () => handleCustomDateChange(handleQuery)
|
||||||
|
|
||||||
const mediaChannelOptionsFlat = 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 []
|
||||||
@@ -258,18 +282,27 @@ const handleDelete = async (id: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
queryParams.start_date = ''
|
resetTimeFilter()
|
||||||
queryParams.end_date = ''
|
|
||||||
queryParams.media_channel_code = ''
|
queryParams.media_channel_code = ''
|
||||||
queryParams.dept_id = ''
|
queryParams.dept_id = ''
|
||||||
queryParams.remark = ''
|
queryParams.remark = ''
|
||||||
resetPage()
|
pager.page = 1
|
||||||
|
pager.size = 15
|
||||||
|
getLists()
|
||||||
}
|
}
|
||||||
|
|
||||||
getLists()
|
onMounted(() => {
|
||||||
|
getLists()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.stats-filter-form {
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.account-cost-channel-select {
|
.account-cost-channel-select {
|
||||||
:deep(.channel-opt-row) {
|
:deep(.channel-opt-row) {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -14,13 +14,14 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card class="!border-none" shadow="never">
|
<el-card class="!border-none" shadow="never">
|
||||||
<el-form class="mb-[-16px]" :model="queryParams" inline>
|
<el-form :inline="true" :model="queryParams" class="stats-filter-form">
|
||||||
<el-form-item label="日期范围">
|
<list-time-filter
|
||||||
<daterange-picker
|
v-model:time-type="time_type"
|
||||||
v-model:startTime="queryParams.start_date"
|
v-model:date-range="dateRange"
|
||||||
v-model:endTime="queryParams.end_date"
|
@time-type-change="onTimeTypeChange"
|
||||||
|
@custom-change="onCustomDateChange"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="部门">
|
<el-form-item label="部门">
|
||||||
<el-tree-select
|
<el-tree-select
|
||||||
v-model="queryParams.dept_id"
|
v-model="queryParams.dept_id"
|
||||||
@@ -33,7 +34,7 @@
|
|||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
class="w-[220px]"
|
class="w-[220px]"
|
||||||
@change="resetPage"
|
@change="handleQuery"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="自媒体来源">
|
<el-form-item label="自媒体来源">
|
||||||
@@ -44,7 +45,7 @@
|
|||||||
placeholder="全部来源"
|
placeholder="全部来源"
|
||||||
:allow-create="false"
|
:allow-create="false"
|
||||||
select-class="w-[220px]"
|
select-class="w-[220px]"
|
||||||
@change="resetPage"
|
@change="handleQuery"
|
||||||
@visible-change="onMediaSourceDropdownVisible"
|
@visible-change="onMediaSourceDropdownVisible"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -53,11 +54,11 @@
|
|||||||
v-model="queryParams.remark"
|
v-model="queryParams.remark"
|
||||||
placeholder="备注 / 创建人"
|
placeholder="备注 / 创建人"
|
||||||
clearable
|
clearable
|
||||||
@keyup.enter="resetPage"
|
@keyup.enter="handleQuery"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" @click="resetPage">查询</el-button>
|
<el-button type="primary" :loading="pager.loading" @click="handleQuery">查询</el-button>
|
||||||
<el-button @click="handleReset">重置</el-button>
|
<el-button @click="handleReset">重置</el-button>
|
||||||
<el-button v-if="selfInputPath" class="ml-2" @click="goSelfInput">返回统计</el-button>
|
<el-button v-if="selfInputPath" class="ml-2" @click="goSelfInput">返回统计</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -146,6 +147,8 @@ import feedback from '@/utils/feedback'
|
|||||||
import CostEditPopup from './cost-edit.vue'
|
import CostEditPopup from './cost-edit.vue'
|
||||||
import MediaSourceSelect from './MediaSourceSelect.vue'
|
import MediaSourceSelect from './MediaSourceSelect.vue'
|
||||||
import { useMediaSourceOptions } from './useMediaSourceOptions'
|
import { useMediaSourceOptions } from './useMediaSourceOptions'
|
||||||
|
import ListTimeFilter from '@/components/list-time-filter/index.vue'
|
||||||
|
import { useListTimeFilter } from '@/hooks/useListTimeFilter'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const editRef = shallowRef<InstanceType<typeof CostEditPopup>>()
|
const editRef = shallowRef<InstanceType<typeof CostEditPopup>>()
|
||||||
@@ -173,25 +176,48 @@ const goSelfInput = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
start_date: '',
|
|
||||||
end_date: '',
|
|
||||||
media_source: '',
|
media_source: '',
|
||||||
dept_id: undefined as number | undefined,
|
dept_id: undefined as number | undefined,
|
||||||
remark: '',
|
remark: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
time_type,
|
||||||
|
dateRange,
|
||||||
|
resolve,
|
||||||
|
handleTimeTypeChange,
|
||||||
|
handleCustomDateChange,
|
||||||
|
resetTimeFilter,
|
||||||
|
} = useListTimeFilter('today')
|
||||||
|
|
||||||
const { pager, getLists, resetPage } = usePaging({
|
const { pager, getLists, resetPage } = usePaging({
|
||||||
fetchFun: personalAccountCostLists,
|
fetchFun: async (params: Record<string, any>) => {
|
||||||
|
const [start_date, end_date] = resolve()
|
||||||
|
return personalAccountCostLists({
|
||||||
|
...params,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
})
|
||||||
|
},
|
||||||
params: queryParams,
|
params: queryParams,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleQuery = () => {
|
||||||
|
pager.page = 1
|
||||||
|
getLists()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTimeTypeChange = () => handleTimeTypeChange(handleQuery)
|
||||||
|
const onCustomDateChange = () => handleCustomDateChange(handleQuery)
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
queryParams.start_date = ''
|
resetTimeFilter()
|
||||||
queryParams.end_date = ''
|
|
||||||
queryParams.media_source = ''
|
queryParams.media_source = ''
|
||||||
queryParams.dept_id = undefined
|
queryParams.dept_id = undefined
|
||||||
queryParams.remark = ''
|
queryParams.remark = ''
|
||||||
resetPage()
|
pager.page = 1
|
||||||
|
pager.size = 15
|
||||||
|
getLists()
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMediaSourceDropdownVisible = (visible: boolean) => {
|
const onMediaSourceDropdownVisible = (visible: boolean) => {
|
||||||
@@ -236,9 +262,10 @@ const loadDeptOptions = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
loadDeptOptions()
|
loadDeptOptions()
|
||||||
loadMediaSourceOptions()
|
loadMediaSourceOptions()
|
||||||
|
await getLists()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -247,6 +274,12 @@ onMounted(() => {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stats-filter-form {
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dept-cell {
|
.dept-cell {
|
||||||
cursor: help;
|
cursor: help;
|
||||||
border-bottom: 1px dashed #c0c4cc;
|
border-bottom: 1px dashed #c0c4cc;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class PrescriptionLibraryLists extends BaseAdminDataLists implements ListsSearch
|
|||||||
public function lists(): array
|
public function lists(): array
|
||||||
{
|
{
|
||||||
$field = [
|
$field = [
|
||||||
'id', 'prescription_name', 'formula_type', 'herbs', 'is_public',
|
'id', 'prescription_name', 'formula_type', 'herbs', 'is_public', 'disable_edit',
|
||||||
'creator_id', 'creator_name', 'create_time', 'update_time'
|
'creator_id', 'creator_name', 'create_time', 'update_time'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -778,6 +778,7 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
|||||||
'export_channel' => '渠道',
|
'export_channel' => '渠道',
|
||||||
'export_guahao_channel_source' => '自媒体渠道(挂号渠道来源)',
|
'export_guahao_channel_source' => '自媒体渠道(挂号渠道来源)',
|
||||||
'export_medication_form' => '药品形态',
|
'export_medication_form' => '药品形态',
|
||||||
|
'export_prescription_name' => '药方名称',
|
||||||
'export_service_package' => '服务套餐',
|
'export_service_package' => '服务套餐',
|
||||||
'export_medication_days' => '天数',
|
'export_medication_days' => '天数',
|
||||||
'export_amount' => '总金额',
|
'export_amount' => '总金额',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use app\common\model\Order;
|
|||||||
use app\common\model\tcm\Diagnosis;
|
use app\common\model\tcm\Diagnosis;
|
||||||
use app\common\model\tcm\DiagnosisAssignLog;
|
use app\common\model\tcm\DiagnosisAssignLog;
|
||||||
use app\common\model\tcm\Prescription;
|
use app\common\model\tcm\Prescription;
|
||||||
|
use app\common\model\tcm\PrescriptionLibrary;
|
||||||
use app\common\model\tcm\PrescriptionOrder;
|
use app\common\model\tcm\PrescriptionOrder;
|
||||||
use app\common\model\tcm\PrescriptionOrderLog;
|
use app\common\model\tcm\PrescriptionOrderLog;
|
||||||
use app\common\model\tcm\PrescriptionOrderPayOrder;
|
use app\common\model\tcm\PrescriptionOrderPayOrder;
|
||||||
@@ -3116,7 +3117,7 @@ class PrescriptionOrderLogic
|
|||||||
$rxById = [];
|
$rxById = [];
|
||||||
if ($rxIdList !== []) {
|
if ($rxIdList !== []) {
|
||||||
$rxRows = Prescription::whereIn('id', $rxIdList)->whereNull('delete_time')
|
$rxRows = Prescription::whereIn('id', $rxIdList)->whereNull('delete_time')
|
||||||
->field(['id', 'prescription_type', 'need_decoction', 'dose_unit', 'assistant_id', 'appointment_id'])
|
->field(['id', 'prescription_type', 'need_decoction', 'dose_unit', 'assistant_id', 'appointment_id', 'prescription_name', 'aux_usage', 'herbs', 'creator_id'])
|
||||||
->select()
|
->select()
|
||||||
->toArray();
|
->toArray();
|
||||||
foreach ($rxRows as $xr) {
|
foreach ($rxRows as $xr) {
|
||||||
@@ -3124,6 +3125,10 @@ class PrescriptionOrderLogic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 药方名称反查:处方库(开方医师 creator_id + 公开模板),按 formula_type + 药材集合匹配,
|
||||||
|
// 复刻前端 resolveSlipAuxLibraryName 口径(主方/辅方均如此;处方记录 prescription_name 仅做兜底)。
|
||||||
|
[$libByDoctor, $libPublic] = self::buildPrescriptionLibraryNameIndexForExport($rxById);
|
||||||
|
|
||||||
if ($needGuahaoChannel) {
|
if ($needGuahaoChannel) {
|
||||||
$yejiChannelHighlightByDiag = [];
|
$yejiChannelHighlightByDiag = [];
|
||||||
if (
|
if (
|
||||||
@@ -3329,6 +3334,22 @@ class PrescriptionOrderLogic
|
|||||||
}
|
}
|
||||||
|
|
||||||
$item['export_medication_form'] = self::formatMedicationFormForExport(\is_array($rx) ? $rx : []);
|
$item['export_medication_form'] = self::formatMedicationFormForExport(\is_array($rx) ? $rx : []);
|
||||||
|
|
||||||
|
// 药方名称:主方/辅方均反查处方库取名称(与处方笺一致),带标签;甘草/无匹配留空
|
||||||
|
[$mainRxName, $auxRxName] = self::resolvePrescriptionNamesForExport(
|
||||||
|
\is_array($rx) ? $rx : [],
|
||||||
|
$libByDoctor,
|
||||||
|
$libPublic
|
||||||
|
);
|
||||||
|
$rxNameParts = [];
|
||||||
|
if ($mainRxName !== '') {
|
||||||
|
$rxNameParts[] = '主方:' . $mainRxName;
|
||||||
|
}
|
||||||
|
if ($auxRxName !== '') {
|
||||||
|
$rxNameParts[] = '辅方:' . $auxRxName;
|
||||||
|
}
|
||||||
|
$item['export_prescription_name'] = implode(' ', $rxNameParts);
|
||||||
|
|
||||||
$item['export_service_package'] = self::formatServicePackageForExport(
|
$item['export_service_package'] = self::formatServicePackageForExport(
|
||||||
$item['service_package'] ?? '',
|
$item['service_package'] ?? '',
|
||||||
$packageNameByValue
|
$packageNameByValue
|
||||||
@@ -3401,6 +3422,186 @@ class PrescriptionOrderLogic
|
|||||||
unset($item);
|
unset($item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出药方名称反查:批量加载相关开方医师处方库(creator_id 命中 + 公开模板),
|
||||||
|
* 建立 [creator_id][formula_type][herbKey] => name 与 公开 [formula_type][herbKey] => name 两级索引。
|
||||||
|
*
|
||||||
|
* @param array<int, array> $rxById 处方 id => 处方行(需含 herbs / creator_id)
|
||||||
|
*
|
||||||
|
* @return array{0: array<int, array<string, array<string, string>>>, 1: array<string, array<string, string>>}
|
||||||
|
*/
|
||||||
|
private static function buildPrescriptionLibraryNameIndexForExport(array $rxById): array
|
||||||
|
{
|
||||||
|
$libByDoctor = [];
|
||||||
|
$libPublic = [];
|
||||||
|
if ($rxById === []) {
|
||||||
|
return [$libByDoctor, $libPublic];
|
||||||
|
}
|
||||||
|
|
||||||
|
$doctorIds = [];
|
||||||
|
foreach ($rxById as $rx) {
|
||||||
|
$cid = (int) ($rx['creator_id'] ?? 0);
|
||||||
|
if ($cid > 0) {
|
||||||
|
$doctorIds[$cid] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$doctorIdList = array_keys($doctorIds);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$libRows = PrescriptionLibrary::whereNull('delete_time')
|
||||||
|
->where(function ($q) use ($doctorIdList) {
|
||||||
|
if ($doctorIdList !== []) {
|
||||||
|
$q->whereIn('creator_id', $doctorIdList)->whereOr('is_public', 1);
|
||||||
|
} else {
|
||||||
|
$q->where('is_public', 1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->field(['prescription_name', 'formula_type', 'herbs', 'creator_id', 'is_public'])
|
||||||
|
->select()
|
||||||
|
->toArray();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('buildPrescriptionLibraryNameIndexForExport failed: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return [$libByDoctor, $libPublic];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($libRows as $lib) {
|
||||||
|
$name = trim((string) ($lib['prescription_name'] ?? ''));
|
||||||
|
if ($name === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$herbsRaw = $lib['herbs'] ?? null;
|
||||||
|
if (\is_string($herbsRaw) && $herbsRaw !== '') {
|
||||||
|
$decoded = json_decode($herbsRaw, true);
|
||||||
|
$herbsRaw = \is_array($decoded) ? $decoded : [];
|
||||||
|
}
|
||||||
|
if (!\is_array($herbsRaw) || $herbsRaw === []) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$key = self::herbSetMatchKeyForExport($herbsRaw);
|
||||||
|
if ($key === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$ft = ((string) ($lib['formula_type'] ?? '')) === '辅方' ? '辅方' : '主方';
|
||||||
|
|
||||||
|
if ((int) ($lib['is_public'] ?? 0) === 1) {
|
||||||
|
$libPublic[$ft][$key] ??= $name;
|
||||||
|
}
|
||||||
|
$cid = (int) ($lib['creator_id'] ?? 0);
|
||||||
|
if ($cid > 0) {
|
||||||
|
$libByDoctor[$cid][$ft][$key] ??= $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$libByDoctor, $libPublic];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反查单张处方的主方 / 辅方名称(与处方笺展示一致):
|
||||||
|
* - 主方:主方药材集合匹配开方医师/公开处方库(formula_type=主方);兜底取处方 prescription_name。
|
||||||
|
* - 辅方:优先 aux_usage.prescription_name / library_name,否则辅方药材集合匹配(formula_type=辅方)。
|
||||||
|
*
|
||||||
|
* @param array $rx 处方行(含 herbs / creator_id / prescription_name / aux_usage)
|
||||||
|
* @param array<int, array<string, array<string, string>>> $libByDoctor
|
||||||
|
* @param array<string, array<string, string>> $libPublic
|
||||||
|
*
|
||||||
|
* @return array{0: string, 1: string} [主方名称, 辅方名称]
|
||||||
|
*/
|
||||||
|
private static function resolvePrescriptionNamesForExport(array $rx, array $libByDoctor, array $libPublic): array
|
||||||
|
{
|
||||||
|
if ($rx === []) {
|
||||||
|
return ['', ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
$doctorId = (int) ($rx['creator_id'] ?? 0);
|
||||||
|
|
||||||
|
$herbs = $rx['herbs'] ?? null;
|
||||||
|
if (\is_string($herbs) && $herbs !== '') {
|
||||||
|
$decoded = json_decode($herbs, true);
|
||||||
|
$herbs = \is_array($decoded) ? $decoded : [];
|
||||||
|
}
|
||||||
|
if (!\is_array($herbs)) {
|
||||||
|
$herbs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$mainHerbs = [];
|
||||||
|
$auxHerbs = [];
|
||||||
|
foreach ($herbs as $h) {
|
||||||
|
if (!\is_array($h)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (((string) ($h['formula_type'] ?? '')) === '辅方') {
|
||||||
|
$auxHerbs[] = $h;
|
||||||
|
} else {
|
||||||
|
$mainHerbs[] = $h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$lookup = static function (string $ft, array $hs) use ($doctorId, $libByDoctor, $libPublic): string {
|
||||||
|
if ($hs === []) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$key = self::herbSetMatchKeyForExport($hs);
|
||||||
|
if ($key === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if ($doctorId > 0 && isset($libByDoctor[$doctorId][$ft][$key])) {
|
||||||
|
return $libByDoctor[$doctorId][$ft][$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) ($libPublic[$ft][$key] ?? '');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 主方
|
||||||
|
$mainName = $lookup('主方', $mainHerbs);
|
||||||
|
if ($mainName === '') {
|
||||||
|
$mainName = trim((string) ($rx['prescription_name'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅方:先读持久化字段
|
||||||
|
$auxName = '';
|
||||||
|
$auxUsage = $rx['aux_usage'] ?? null;
|
||||||
|
if (\is_string($auxUsage) && $auxUsage !== '') {
|
||||||
|
$decodedAux = json_decode($auxUsage, true);
|
||||||
|
$auxUsage = \is_array($decodedAux) ? $decodedAux : null;
|
||||||
|
}
|
||||||
|
if (\is_array($auxUsage)) {
|
||||||
|
$auxName = trim((string) ($auxUsage['prescription_name'] ?? ($auxUsage['library_name'] ?? '')));
|
||||||
|
}
|
||||||
|
if ($auxName === '') {
|
||||||
|
$auxName = $lookup('辅方', $auxHerbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$mainName, $auxName];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 药材集合匹配 key:每味 name:dosage(dosage 规整去尾零)排序后拼接,与处方库反查双方同口径。
|
||||||
|
*/
|
||||||
|
private static function herbSetMatchKeyForExport(array $herbs): string
|
||||||
|
{
|
||||||
|
$parts = [];
|
||||||
|
foreach ($herbs as $h) {
|
||||||
|
if (!\is_array($h)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = trim((string) ($h['name'] ?? ''));
|
||||||
|
if ($name === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$dosage = (float) ($h['dosage'] ?? 0);
|
||||||
|
if (floor($dosage) === $dosage) {
|
||||||
|
$dStr = (string) (int) $dosage;
|
||||||
|
} else {
|
||||||
|
$dStr = rtrim(rtrim(number_format($dosage, 4, '.', ''), '0'), '.');
|
||||||
|
}
|
||||||
|
$parts[] = $name . ':' . $dStr;
|
||||||
|
}
|
||||||
|
sort($parts);
|
||||||
|
|
||||||
|
return implode('|', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 完成订单时把关联诊单的 assistant_id 清空,让患者回到「待分配」状态
|
* 完成订单时把关联诊单的 assistant_id 清空,让患者回到「待分配」状态
|
||||||
* 仅当该患者没有其它进行中的处方订单且无指派日志时才清空,避免误覆盖
|
* 仅当该患者没有其它进行中的处方订单且无指派日志时才清空,避免误覆盖
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ class PrescriptionLibraryValidate extends BaseValidate
|
|||||||
'prescription_name' => 'require|max:100',
|
'prescription_name' => 'require|max:100',
|
||||||
'formula_type' => 'in:主方,辅方',
|
'formula_type' => 'in:主方,辅方',
|
||||||
'herbs' => 'require|array',
|
'herbs' => 'require|array',
|
||||||
'is_public' => 'in:0,1'
|
'is_public' => 'in:0,1',
|
||||||
|
'disable_edit' => 'in:0,1'
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $message = [
|
protected $message = [
|
||||||
@@ -27,7 +28,8 @@ class PrescriptionLibraryValidate extends BaseValidate
|
|||||||
'formula_type.in' => '处方类型只能是主方或辅方',
|
'formula_type.in' => '处方类型只能是主方或辅方',
|
||||||
'herbs.require' => '药材列表不能为空',
|
'herbs.require' => '药材列表不能为空',
|
||||||
'herbs.array' => '药材列表格式错误',
|
'herbs.array' => '药材列表格式错误',
|
||||||
'is_public.in' => '是否公开参数错误'
|
'is_public.in' => '是否公开参数错误',
|
||||||
|
'disable_edit.in' => '禁用修改参数错误'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,7 +37,7 @@ class PrescriptionLibraryValidate extends BaseValidate
|
|||||||
*/
|
*/
|
||||||
public function sceneAdd()
|
public function sceneAdd()
|
||||||
{
|
{
|
||||||
return $this->only(['prescription_name', 'formula_type', 'herbs', 'is_public']);
|
return $this->only(['prescription_name', 'formula_type', 'herbs', 'is_public', 'disable_edit']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +45,7 @@ class PrescriptionLibraryValidate extends BaseValidate
|
|||||||
*/
|
*/
|
||||||
public function sceneEdit()
|
public function sceneEdit()
|
||||||
{
|
{
|
||||||
return $this->only(['id', 'prescription_name', 'formula_type', 'herbs', 'is_public']);
|
return $this->only(['id', 'prescription_name', 'formula_type', 'herbs', 'is_public', 'disable_edit']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- 处方库:禁用修改(导入处方后锁定药材,不可增删改,只能再次导入覆盖)
|
||||||
|
ALTER TABLE `zyt_prescription_library`
|
||||||
|
ADD COLUMN `disable_edit` tinyint(1) NOT NULL DEFAULT 0 COMMENT '导入处方后是否禁止修改/删除药材:0否 1是' AFTER `is_public`;
|
||||||
Reference in New Issue
Block a user