更新
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -407,20 +407,35 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rx-herbs">
|
<div class="rx-herbs">
|
||||||
|
<template v-if="slipMainHerbs.length">
|
||||||
|
<div class="rx-herb-section-label">主方</div>
|
||||||
<div
|
<div
|
||||||
v-for="(h, i) in slipHerbsList"
|
v-for="(h, i) in slipMainHerbs"
|
||||||
:key="i"
|
:key="'main-' + i"
|
||||||
class="rx-herb-cell"
|
class="rx-herb-cell"
|
||||||
>
|
>
|
||||||
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }}克)</span>
|
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }}克)</span>
|
||||||
<span class="rx-herb-total">{{ rxHerbTotal(h.dosage) }}克</span>
|
<span class="rx-herb-total">{{ rxHerbTotal(h.dosage) }}克</span>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="slipAuxHerbs.length">
|
||||||
|
<div class="rx-herb-section-label rx-herb-section-label--aux">辅方</div>
|
||||||
|
<div
|
||||||
|
v-for="(h, i) in slipAuxHerbs"
|
||||||
|
:key="'aux-' + i"
|
||||||
|
class="rx-herb-cell"
|
||||||
|
>
|
||||||
|
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }}克)</span>
|
||||||
|
<span class="rx-herb-total">{{ rxHerbTotal(h.dosage) }}克</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 服法 / 医嘱 / 备注 / 药房备注 / 出丸 -->
|
<!-- 服法 / 医嘱 / 备注 / 药房备注 / 出丸 -->
|
||||||
<div class="rx-text">
|
<div class="rx-text">
|
||||||
<p>服法:{{ rxUsageText }}</p>
|
<p>主方服法:{{ rxUsageText }}</p>
|
||||||
|
<p v-if="rxAuxUsageText">辅方服法:{{ rxAuxUsageText }}</p>
|
||||||
<p v-if="rxAdviceText">医嘱:{{ rxAdviceText }}</p>
|
<p v-if="rxAdviceText">医嘱:{{ rxAdviceText }}</p>
|
||||||
<p v-if="rxRemarkText">备注:{{ rxRemarkText }}</p>
|
<p v-if="rxRemarkText">备注:{{ rxRemarkText }}</p>
|
||||||
<p v-if="rxPharmacyRemarkText" class="rx-text-warn">
|
<p v-if="rxPharmacyRemarkText" class="rx-text-warn">
|
||||||
@@ -649,32 +664,38 @@
|
|||||||
<el-form-item label="药材配方" prop="herbs" required>
|
<el-form-item label="药材配方" prop="herbs" required>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<el-button type="primary" size="small" @click="addHerb">
|
<el-button
|
||||||
添加药材
|
type="success"
|
||||||
</el-button>
|
size="small"
|
||||||
<el-button type="success" size="small" @click="openLibraryDialog">
|
v-perms="['cf.prescription/add', 'cf.prescription/edit', 'tcm.prescriptionLibrary/lists']"
|
||||||
|
@click="openLibraryDialog"
|
||||||
|
>
|
||||||
从处方库导入
|
从处方库导入
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
|
||||||
<div class="mt-2">
|
|
||||||
<el-button type="warning" size="small" plain :icon="EditPen" @click="openPasteRecipeDialog">
|
<el-button type="warning" size="small" plain :icon="EditPen" @click="openPasteRecipeDialog">
|
||||||
导入药方
|
导入药方
|
||||||
</el-button>
|
</el-button>
|
||||||
<span class="text-xs text-gray-500 ml-2 align-middle">
|
<span class="text-xs text-gray-500">
|
||||||
粘贴文本解析药名与剂量;须与药品库名称完全一致才录入
|
粘贴文本解析药名与剂量;须与药品库名称完全一致才录入(导入至主方)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="editForm.herbs" class="mt-2" border>
|
|
||||||
|
<div class="herb-formula-block mt-3">
|
||||||
|
<div class="herb-formula-block__head">
|
||||||
|
<el-tag type="primary" size="small">主方</el-tag>
|
||||||
|
<el-button type="primary" size="small" @click="addHerb('主方')">添加主方药材</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="mainHerbRows" class="mt-2" border empty-text="暂无主方药材">
|
||||||
<el-table-column label="序号" type="index" width="60" />
|
<el-table-column label="序号" 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="row.name" class="w-full" />
|
<MedicineNameSelect v-model="editForm.herbs[row.index].name" 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">
|
||||||
<template #default="{ row, $index }">
|
<template #default="{ row }">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="row.dosage"
|
v-model="editForm.herbs[row.index].dosage"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="1"
|
:precision="1"
|
||||||
:step="0.5"
|
:step="0.5"
|
||||||
@@ -684,18 +705,46 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="80">
|
<el-table-column label="操作" width="80">
|
||||||
<template #default="{ $index }">
|
<template #default="{ row }">
|
||||||
<el-button
|
<el-button type="danger" link @click="removeHerb(row.index)">删除</el-button>
|
||||||
type="danger"
|
|
||||||
link
|
|
||||||
@click="removeHerb($index)"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
</el-table>
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="herb-formula-block mt-4">
|
||||||
|
<div class="herb-formula-block__head">
|
||||||
|
<el-tag type="warning" size="small">辅方</el-tag>
|
||||||
|
<el-button type="primary" size="small" plain @click="addHerb('辅方')">
|
||||||
|
添加辅方药材
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="auxHerbRows" class="mt-2" border empty-text="暂无辅方药材">
|
||||||
|
<el-table-column label="序号" type="index" width="60" />
|
||||||
|
<el-table-column label="药材名称" min-width="220">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<MedicineNameSelect v-model="editForm.herbs[row.index].name" class="w-full" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="剂量(克)" min-width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="editForm.herbs[row.index].dosage"
|
||||||
|
:min="0"
|
||||||
|
:precision="1"
|
||||||
|
:step="0.5"
|
||||||
|
placeholder="剂量"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="danger" link @click="removeHerb(row.index)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
<el-alert
|
<el-alert
|
||||||
v-if="editMode === 'edit'"
|
v-if="editMode === 'edit'"
|
||||||
type="warning"
|
type="warning"
|
||||||
@@ -778,8 +827,10 @@
|
|||||||
<el-option label="汤剂" value="汤剂" />
|
<el-option label="汤剂" value="汤剂" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item> </el-col>
|
</el-form-item> </el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<!-- 用量字段 -->
|
<div class="usage-formula-title usage-formula-title--main">主方用法</div>
|
||||||
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-form-item label="用量" prop="dosage_amount">
|
<el-form-item label="用量" prop="dosage_amount">
|
||||||
<!-- 浓缩水丸:1-10g 下拉选择 -->
|
<!-- 浓缩水丸:1-10g 下拉选择 -->
|
||||||
@@ -882,6 +933,97 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<template v-if="auxHerbRows.length > 0">
|
||||||
|
<div class="usage-formula-title usage-formula-title--aux">辅方用法</div>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="用量">
|
||||||
|
<el-select
|
||||||
|
v-if="editForm.prescription_type === '浓缩水丸'"
|
||||||
|
v-model="editForm.aux_usage.dosage_amount"
|
||||||
|
placeholder="请选择用量"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<el-option v-for="n in 10" :key="n" :label="n + 'g'" :value="n" />
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
|
v-else-if="editForm.prescription_type === '饮片'"
|
||||||
|
v-model="editForm.aux_usage.dosage_amount"
|
||||||
|
placeholder="请选择用量"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<el-option label="50ml" :value="50" />
|
||||||
|
<el-option label="100ml" :value="100" />
|
||||||
|
<el-option label="120ml" :value="120" />
|
||||||
|
<el-option label="150ml" :value="150" />
|
||||||
|
<el-option label="180ml" :value="180" />
|
||||||
|
<el-option label="200ml" :value="200" />
|
||||||
|
<el-option label="250ml" :value="250" />
|
||||||
|
</el-select>
|
||||||
|
<el-input-number
|
||||||
|
v-else
|
||||||
|
v-model="editForm.aux_usage.dosage_amount"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="editForm.prescription_type === '浓缩水丸'" :span="8">
|
||||||
|
<el-form-item label=" " label-width="12px">
|
||||||
|
<el-select
|
||||||
|
v-model="editForm.aux_usage.dosage_bag_count"
|
||||||
|
placeholder="请选择袋数"
|
||||||
|
class="w-[120px]"
|
||||||
|
>
|
||||||
|
<el-option label="1袋" :value="1" />
|
||||||
|
<el-option label="2袋" :value="2" />
|
||||||
|
<el-option label="3袋" :value="3" />
|
||||||
|
<el-option label="4袋" :value="4" />
|
||||||
|
<el-option label="5袋" :value="5" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="editForm.prescription_type === '饮片'" :span="8">
|
||||||
|
<el-form-item label="是否代煎">
|
||||||
|
<el-radio-group v-model="editForm.aux_usage.need_decoction">
|
||||||
|
<el-radio :label="true">代煎</el-radio>
|
||||||
|
<el-radio :label="false">不代煎</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="editForm.prescription_type === '饮片'" :span="8">
|
||||||
|
<el-form-item label="每贴出包数">
|
||||||
|
<el-select v-model="editForm.aux_usage.bags_per_dose" placeholder="请选择" class="w-full">
|
||||||
|
<el-option v-for="n in 9" :key="n" :label="n + '包'" :value="n" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="每天几次">
|
||||||
|
<el-input-number
|
||||||
|
v-model="editForm.aux_usage.times_per_day"
|
||||||
|
:min="1"
|
||||||
|
:max="6"
|
||||||
|
class="!w-full"
|
||||||
|
placeholder="每天服用次数"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="服用天数">
|
||||||
|
<el-input-number
|
||||||
|
v-model="editForm.aux_usage.usage_days"
|
||||||
|
:min="1"
|
||||||
|
:max="365"
|
||||||
|
placeholder="服用天数"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
<el-form-item label="用法" prop="usage_instruction">
|
<el-form-item label="用法" prop="usage_instruction">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="editForm.usage_instruction"
|
v-model="editForm.usage_instruction"
|
||||||
@@ -1365,16 +1507,17 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 从处方库导入(与诊间 tcm-prescription 一致,按当前处方开方医师 creator_id 筛选) -->
|
<!-- 从处方库导入:当前开方医师的全部模板 + 所有人可见的公共模板 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="showLibraryDialog"
|
v-model="showLibraryDialog"
|
||||||
title="从处方库导入"
|
title="从处方库导入"
|
||||||
width="800px"
|
width="800px"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
>
|
>
|
||||||
<div class="mb-4">
|
<div class="mb-4 flex gap-3">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="librarySearchName"
|
v-model="librarySearchName"
|
||||||
|
class="flex-1"
|
||||||
placeholder="请输入处方名称搜索"
|
placeholder="请输入处方名称搜索"
|
||||||
clearable
|
clearable
|
||||||
@keyup.enter="searchLibrary"
|
@keyup.enter="searchLibrary"
|
||||||
@@ -1384,6 +1527,24 @@
|
|||||||
<el-button :icon="Search" @click="searchLibrary" />
|
<el-button :icon="Search" @click="searchLibrary" />
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
<el-select
|
||||||
|
v-model="librarySearchFormulaType"
|
||||||
|
placeholder="处方类型"
|
||||||
|
clearable
|
||||||
|
class="w-[140px]"
|
||||||
|
@change="searchLibrary"
|
||||||
|
>
|
||||||
|
<el-option label="全部" value="" />
|
||||||
|
<el-option label="主方" value="主方" />
|
||||||
|
<el-option label="辅方" value="辅方" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 flex flex-wrap items-center gap-x-4 gap-y-2">
|
||||||
|
<span class="text-sm text-gray-600 shrink-0">导入方式</span>
|
||||||
|
<el-radio-group v-model="libraryImportMode">
|
||||||
|
<el-radio label="replace">覆盖现有药材</el-radio>
|
||||||
|
<el-radio label="append">追加到末尾</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
@@ -1394,6 +1555,13 @@
|
|||||||
@row-click="handleSelectLibraryRow"
|
@row-click="handleSelectLibraryRow"
|
||||||
>
|
>
|
||||||
<el-table-column label="处方名称" prop="prescription_name" min-width="150" show-overflow-tooltip />
|
<el-table-column label="处方名称" prop="prescription_name" min-width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column label="处方类型" width="90">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.formula_type === '辅方' ? 'warning' : 'primary'" size="small">
|
||||||
|
{{ row.formula_type || '主方' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="药材数量" width="100">
|
<el-table-column label="药材数量" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ row.herbs?.length || 0 }}味
|
{{ row.herbs?.length || 0 }}味
|
||||||
@@ -1407,6 +1575,13 @@
|
|||||||
<span v-else class="text-gray-400">暂无药材</span>
|
<span v-else class="text-gray-400">暂无药材</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="是否公开" width="110">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.is_public ? 'success' : 'info'" size="small">
|
||||||
|
{{ row.is_public ? '所有人可见' : '仅自己可见' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="创建人" prop="creator_name" width="100" />
|
<el-table-column label="创建人" prop="creator_name" width="100" />
|
||||||
<el-table-column label="操作" width="100" fixed="right">
|
<el-table-column label="操作" width="100" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@@ -1522,6 +1697,119 @@ import jsPDF from 'jspdf'
|
|||||||
|
|
||||||
const TcmDiagnosisEditView = defineAsyncComponent(() => import('@/views/tcm/diagnosis/edit.vue'))
|
const TcmDiagnosisEditView = defineAsyncComponent(() => import('@/views/tcm/diagnosis/edit.vue'))
|
||||||
|
|
||||||
|
type FormulaType = '主方' | '辅方'
|
||||||
|
type HerbRow = { name: string; dosage: number; formula_type: FormulaType }
|
||||||
|
|
||||||
|
type AuxUsageForm = {
|
||||||
|
dosage_amount?: number
|
||||||
|
dosage_bag_count: number
|
||||||
|
need_decoction: boolean
|
||||||
|
bags_per_dose: number
|
||||||
|
times_per_day: number
|
||||||
|
usage_days: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultAuxUsage(prescriptionType = '浓缩水丸'): AuxUsageForm {
|
||||||
|
if (prescriptionType === '饮片') {
|
||||||
|
return {
|
||||||
|
dosage_amount: 50,
|
||||||
|
dosage_bag_count: 1,
|
||||||
|
need_decoction: false,
|
||||||
|
bags_per_dose: 1,
|
||||||
|
times_per_day: 3,
|
||||||
|
usage_days: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prescriptionType === '浓缩水丸') {
|
||||||
|
return {
|
||||||
|
dosage_amount: 5,
|
||||||
|
dosage_bag_count: 1,
|
||||||
|
need_decoction: false,
|
||||||
|
bags_per_dose: 1,
|
||||||
|
times_per_day: 3,
|
||||||
|
usage_days: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
dosage_amount: 1,
|
||||||
|
dosage_bag_count: 1,
|
||||||
|
need_decoction: false,
|
||||||
|
bags_per_dose: 1,
|
||||||
|
times_per_day: 3,
|
||||||
|
usage_days: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAuxUsageForm(raw: unknown, prescriptionType: string): AuxUsageForm {
|
||||||
|
const base = defaultAuxUsage(prescriptionType)
|
||||||
|
if (!raw || typeof raw !== 'object') return { ...base }
|
||||||
|
const o = raw as Record<string, unknown>
|
||||||
|
return {
|
||||||
|
dosage_amount:
|
||||||
|
o.dosage_amount !== null && o.dosage_amount !== undefined && o.dosage_amount !== ''
|
||||||
|
? Number(o.dosage_amount)
|
||||||
|
: base.dosage_amount,
|
||||||
|
dosage_bag_count: o.dosage_bag_count != null ? Number(o.dosage_bag_count) || 1 : base.dosage_bag_count,
|
||||||
|
need_decoction: o.need_decoction === 1 || o.need_decoction === true,
|
||||||
|
bags_per_dose: o.bags_per_dose != null ? Number(o.bags_per_dose) || 1 : base.bags_per_dose,
|
||||||
|
times_per_day: o.times_per_day != null ? Number(o.times_per_day) || 3 : base.times_per_day,
|
||||||
|
usage_days: o.usage_days != null ? Number(o.usage_days) || 7 : base.usage_days
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUsageSegmentText(
|
||||||
|
usage: {
|
||||||
|
prescription_type?: string
|
||||||
|
dosage_amount?: number | null
|
||||||
|
dosage_unit?: string
|
||||||
|
dosage_bag_count?: number
|
||||||
|
times_per_day?: number
|
||||||
|
usage_way?: string
|
||||||
|
usage_time?: string
|
||||||
|
},
|
||||||
|
fallbackWay?: string,
|
||||||
|
fallbackTime?: string
|
||||||
|
): string {
|
||||||
|
const pt = usage.prescription_type || '浓缩水丸'
|
||||||
|
const times = Number(usage.times_per_day) > 0 ? Number(usage.times_per_day) : 3
|
||||||
|
const amount = usage.dosage_amount != null ? Number(usage.dosage_amount) : 10
|
||||||
|
const unit = usage.dosage_unit || (pt === '饮片' ? 'ml' : 'g')
|
||||||
|
const usageWay = usage.usage_way || fallbackWay || '温水送服'
|
||||||
|
const usageTime = usage.usage_time || fallbackTime || ''
|
||||||
|
const seg: string[] = []
|
||||||
|
seg.push(`每天${times}次`)
|
||||||
|
if (pt === '浓缩水丸') {
|
||||||
|
const bags = Number(usage.dosage_bag_count) > 0 ? Number(usage.dosage_bag_count) : 1
|
||||||
|
seg.push(`一次${bags}袋`)
|
||||||
|
seg.push(`每袋${amount}${unit}`)
|
||||||
|
} else {
|
||||||
|
seg.push(`一次${amount}${unit}`)
|
||||||
|
}
|
||||||
|
seg.push(usageWay)
|
||||||
|
if (usageTime) seg.push(usageTime)
|
||||||
|
return seg.join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFormulaType(v: unknown): FormulaType {
|
||||||
|
return v === '辅方' ? '辅方' : '主方'
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHerbRow(raw: any): HerbRow {
|
||||||
|
return {
|
||||||
|
name: String(raw?.name ?? '').trim(),
|
||||||
|
dosage: Number(raw?.dosage) || 0,
|
||||||
|
formula_type: normalizeFormulaType(raw?.formula_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSlipHerbs(raw: unknown): HerbRow[] {
|
||||||
|
if (!raw) return []
|
||||||
|
if (Array.isArray(raw)) {
|
||||||
|
return raw.map((x: any) => normalizeHerbRow(x))
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
/** 与 server/config/project.php prescription_audit_roles 保持一致 */
|
/** 与 server/config/project.php prescription_audit_roles 保持一致 */
|
||||||
const PRESCRIPTION_AUDIT_ROLE_IDS = [0, 3]
|
const PRESCRIPTION_AUDIT_ROLE_IDS = [0, 3]
|
||||||
|
|
||||||
@@ -1923,7 +2211,13 @@ const createOrderHerbSummary = computed(() => {
|
|||||||
const row = createOrderPrescription.value
|
const row = createOrderPrescription.value
|
||||||
const herbs = normalizeSlipHerbs(row?.herbs)
|
const herbs = normalizeSlipHerbs(row?.herbs)
|
||||||
if (!herbs.length) return '暂无药材明细'
|
if (!herbs.length) return '暂无药材明细'
|
||||||
const s = herbs.map((h) => `${h.name} ${h.dosage}g`).join('、')
|
const main = herbs.filter((h) => normalizeFormulaType(h.formula_type) === '主方')
|
||||||
|
const aux = herbs.filter((h) => normalizeFormulaType(h.formula_type) === '辅方')
|
||||||
|
const fmt = (list: HerbRow[]) => list.map((h) => `${h.name} ${h.dosage}g`).join('、')
|
||||||
|
const parts: string[] = []
|
||||||
|
if (main.length) parts.push(`主方:${fmt(main)}`)
|
||||||
|
if (aux.length) parts.push(`辅方:${fmt(aux)}`)
|
||||||
|
const s = parts.join(';')
|
||||||
return s.length > 120 ? `${s.slice(0, 120)}…` : s
|
return s.length > 120 ? `${s.slice(0, 120)}…` : s
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2163,9 +2457,27 @@ const editReviveHint = computed(() => {
|
|||||||
|
|
||||||
const slipHerbsList = computed(() => {
|
const slipHerbsList = computed(() => {
|
||||||
const h = slipView.value?.herbs
|
const h = slipView.value?.herbs
|
||||||
return Array.isArray(h) ? h : []
|
return Array.isArray(h) ? (h as HerbRow[]) : []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const slipMainHerbs = computed(() =>
|
||||||
|
slipHerbsList.value.filter((h) => normalizeFormulaType(h.formula_type) === '主方')
|
||||||
|
)
|
||||||
|
const slipAuxHerbs = computed(() =>
|
||||||
|
slipHerbsList.value.filter((h) => normalizeFormulaType(h.formula_type) === '辅方')
|
||||||
|
)
|
||||||
|
|
||||||
|
const mainHerbRows = computed(() =>
|
||||||
|
editForm.herbs
|
||||||
|
.map((herb, index) => ({ herb, index }))
|
||||||
|
.filter(({ herb }) => normalizeFormulaType(herb.formula_type) === '主方')
|
||||||
|
)
|
||||||
|
const auxHerbRows = computed(() =>
|
||||||
|
editForm.herbs
|
||||||
|
.map((herb, index) => ({ herb, index }))
|
||||||
|
.filter(({ herb }) => normalizeFormulaType(herb.formula_type) === '辅方')
|
||||||
|
)
|
||||||
|
|
||||||
const slipDietaryText = computed(() => {
|
const slipDietaryText = computed(() => {
|
||||||
const d = slipView.value?.dietary_taboo
|
const d = slipView.value?.dietary_taboo
|
||||||
if (Array.isArray(d)) return d.filter(Boolean).join('、')
|
if (Array.isArray(d)) return d.filter(Boolean).join('、')
|
||||||
@@ -2251,23 +2563,26 @@ const rxUsageText = computed(() => {
|
|||||||
const v = slipView.value as any
|
const v = slipView.value as any
|
||||||
if (!v) return '—'
|
if (!v) return '—'
|
||||||
if (v.usage_text) return v.usage_text
|
if (v.usage_text) return v.usage_text
|
||||||
const times = Number(v.times_per_day) > 0 ? Number(v.times_per_day) : 3
|
return buildUsageSegmentText(v)
|
||||||
const amount = v.dosage_amount != null ? Number(v.dosage_amount) : 10
|
})
|
||||||
const unit = v.dosage_unit || 'g'
|
|
||||||
const usageWay = v.usage_way || '温水送服'
|
const rxAuxUsageText = computed(() => {
|
||||||
const usageTime = v.usage_time || ''
|
const v = slipView.value as any
|
||||||
const seg: string[] = []
|
if (!v || !slipAuxHerbs.value.length) return ''
|
||||||
seg.push(`每天${times}次`)
|
const aux = normalizeAuxUsageForm(v.aux_usage, v.prescription_type || '浓缩水丸')
|
||||||
if ((v.prescription_type || '浓缩水丸') === '浓缩水丸') {
|
return buildUsageSegmentText(
|
||||||
const bags = Number(v.dosage_bag_count) > 0 ? Number(v.dosage_bag_count) : 1
|
{
|
||||||
seg.push(`一次${bags}袋`)
|
prescription_type: v.prescription_type,
|
||||||
seg.push(`每袋${amount}${unit}`)
|
dosage_amount: aux.dosage_amount,
|
||||||
} else {
|
dosage_unit: v.dosage_unit,
|
||||||
seg.push(`一次${amount}${unit}`)
|
dosage_bag_count: aux.dosage_bag_count,
|
||||||
}
|
times_per_day: aux.times_per_day,
|
||||||
seg.push(usageWay)
|
usage_way: v.usage_way,
|
||||||
if (usageTime) seg.push(usageTime)
|
usage_time: v.usage_time
|
||||||
return seg.join(', ')
|
},
|
||||||
|
v.usage_way,
|
||||||
|
v.usage_time
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const rxAdviceText = computed(() => {
|
const rxAdviceText = computed(() => {
|
||||||
@@ -2462,7 +2777,7 @@ const editForm = reactive({
|
|||||||
pulse: '',
|
pulse: '',
|
||||||
pulse_condition: '',
|
pulse_condition: '',
|
||||||
clinical_diagnosis: '',
|
clinical_diagnosis: '',
|
||||||
herbs: [] as Array<{ name: string; dosage: number }>,
|
herbs: [] as HerbRow[],
|
||||||
dose_count: 7,
|
dose_count: 7,
|
||||||
dose_unit: '剂',
|
dose_unit: '剂',
|
||||||
usage_days: 7,
|
usage_days: 7,
|
||||||
@@ -2488,7 +2803,8 @@ const editForm = reactive({
|
|||||||
/** 列表带入:业务订单「处方审核」驳回(消费者处方本身可能仍为已通过) */
|
/** 列表带入:业务订单「处方审核」驳回(消费者处方本身可能仍为已通过) */
|
||||||
business_prescription_audit_rejected: 0,
|
business_prescription_audit_rejected: 0,
|
||||||
business_prescription_audit_remark: '',
|
business_prescription_audit_remark: '',
|
||||||
times_per_day: 2
|
times_per_day: 2,
|
||||||
|
aux_usage: defaultAuxUsage('浓缩水丸')
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 编辑页:关联业务订单上的服用天数、医助备注(与处方提示一致) */
|
/** 编辑页:关联业务订单上的服用天数、医助备注(与处方提示一致) */
|
||||||
@@ -2579,6 +2895,7 @@ watch(() => editForm.prescription_type, (newType) => {
|
|||||||
editForm.need_decoction = false
|
editForm.need_decoction = false
|
||||||
editForm.bags_per_dose = 1
|
editForm.bags_per_dose = 1
|
||||||
}
|
}
|
||||||
|
Object.assign(editForm.aux_usage, defaultAuxUsage(newType))
|
||||||
})
|
})
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
@@ -2807,15 +3124,15 @@ function slipAgeText(age: unknown) {
|
|||||||
return `${age}岁`
|
return `${age}岁`
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSlipHerbs(raw: unknown): Array<{ name: string; dosage: number }> {
|
function herbValidationLabel(globalIndex: number): string {
|
||||||
if (!raw) return []
|
const herb = editForm.herbs[globalIndex]
|
||||||
if (Array.isArray(raw)) {
|
if (!herb) return `第${globalIndex + 1}味`
|
||||||
return raw.map((x: any) => ({
|
const ft = normalizeFormulaType(herb.formula_type)
|
||||||
name: String(x?.name ?? '').trim(),
|
let n = 0
|
||||||
dosage: Number(x?.dosage) || 0
|
for (let i = 0; i <= globalIndex; i++) {
|
||||||
}))
|
if (normalizeFormulaType(editForm.herbs[i]?.formula_type) === ft) n++
|
||||||
}
|
}
|
||||||
return []
|
return `${ft}第${n}味`
|
||||||
}
|
}
|
||||||
|
|
||||||
function listRxOrderWarnings(row: any): string[] {
|
function listRxOrderWarnings(row: any): string[] {
|
||||||
@@ -3038,6 +3355,8 @@ const showLibraryDialog = ref(false)
|
|||||||
const libraryLoading = ref(false)
|
const libraryLoading = ref(false)
|
||||||
const libraryList = ref<any[]>([])
|
const libraryList = ref<any[]>([])
|
||||||
const librarySearchName = ref('')
|
const librarySearchName = ref('')
|
||||||
|
const librarySearchFormulaType = ref('')
|
||||||
|
const libraryImportMode = ref<'replace' | 'append'>('replace')
|
||||||
const libraryPage = ref(1)
|
const libraryPage = ref(1)
|
||||||
const libraryPageSize = ref(15)
|
const libraryPageSize = ref(15)
|
||||||
const libraryTotal = ref(0)
|
const libraryTotal = ref(0)
|
||||||
@@ -3082,8 +3401,8 @@ const loadLibraryList = async () => {
|
|||||||
page_no: libraryPage.value,
|
page_no: libraryPage.value,
|
||||||
page_size: libraryPageSize.value,
|
page_size: libraryPageSize.value,
|
||||||
prescription_name: librarySearchName.value,
|
prescription_name: librarySearchName.value,
|
||||||
is_public: '',
|
formula_type: librarySearchFormulaType.value,
|
||||||
creator_id: doctorId
|
prescribing_creator_id: doctorId
|
||||||
})
|
})
|
||||||
libraryList.value = res?.lists || []
|
libraryList.value = res?.lists || []
|
||||||
libraryTotal.value = res?.count || 0
|
libraryTotal.value = res?.count || 0
|
||||||
@@ -3118,18 +3437,33 @@ const handleImportLibrary = (row: any) => {
|
|||||||
feedback.msgWarning('该处方没有药材信息')
|
feedback.msgWarning('该处方没有药材信息')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const imported = JSON.parse(JSON.stringify(row.herbs)) as Array<{ name: string; dosage: number }>
|
const formulaType = normalizeFormulaType(row.formula_type)
|
||||||
editForm.herbs = imported
|
const imported = (JSON.parse(JSON.stringify(row.herbs)) as any[]).map((h) => ({
|
||||||
|
...normalizeHerbRow(h),
|
||||||
|
formula_type: formulaType
|
||||||
|
}))
|
||||||
|
if (libraryImportMode.value === 'replace') {
|
||||||
|
const kept = editForm.herbs.filter((h) => normalizeFormulaType(h.formula_type) !== formulaType)
|
||||||
|
editForm.herbs = [...kept, ...imported]
|
||||||
|
} else {
|
||||||
|
editForm.herbs.push(...imported)
|
||||||
|
}
|
||||||
|
const modeHint = libraryImportMode.value === 'append' ? '(已追加)' : ''
|
||||||
if (findDuplicateHerbNamesLocal().length) {
|
if (findDuplicateHerbNamesLocal().length) {
|
||||||
feedback.msgWarning('导入的处方中存在重复药名,请合并剂量或删除多余行')
|
feedback.msgWarning(
|
||||||
|
`已导入处方「${row.prescription_name}」${modeHint},共${imported.length}味药材。存在重复药名,请合并剂量或删除多余行`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
feedback.msgSuccess(`已导入处方「${row.prescription_name}」${modeHint},共${imported.length}味药材`)
|
||||||
}
|
}
|
||||||
feedback.msgSuccess(`已导入处方「${row.prescription_name}」,共${imported.length}味药材`)
|
|
||||||
showLibraryDialog.value = false
|
showLibraryDialog.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(showLibraryDialog, (open) => {
|
watch(showLibraryDialog, (open) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
librarySearchName.value = ''
|
librarySearchName.value = ''
|
||||||
|
librarySearchFormulaType.value = ''
|
||||||
|
libraryImportMode.value = 'replace'
|
||||||
libraryPage.value = 1
|
libraryPage.value = 1
|
||||||
loadLibraryList()
|
loadLibraryList()
|
||||||
}
|
}
|
||||||
@@ -3261,7 +3595,7 @@ async function handlePasteRecipeImport() {
|
|||||||
}
|
}
|
||||||
pasteRecipeImportLoading.value = true
|
pasteRecipeImportLoading.value = true
|
||||||
try {
|
try {
|
||||||
const resolved: Array<{ name: string; dosage: number }> = []
|
const resolved: HerbRow[] = []
|
||||||
const skippedNames: string[] = []
|
const skippedNames: string[] = []
|
||||||
for (const row of parsed) {
|
for (const row of parsed) {
|
||||||
const name = await resolvePasteHerbNameFromLibrary(row.name)
|
const name = await resolvePasteHerbNameFromLibrary(row.name)
|
||||||
@@ -3269,7 +3603,7 @@ async function handlePasteRecipeImport() {
|
|||||||
skippedNames.push(row.name.trim())
|
skippedNames.push(row.name.trim())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
resolved.push({ name, dosage: row.dosage })
|
resolved.push({ name, dosage: row.dosage, formula_type: '主方' })
|
||||||
}
|
}
|
||||||
const skippedUnique = [...new Set(skippedNames.filter(Boolean))]
|
const skippedUnique = [...new Set(skippedNames.filter(Boolean))]
|
||||||
if (resolved.length === 0) {
|
if (resolved.length === 0) {
|
||||||
@@ -3281,7 +3615,8 @@ async function handlePasteRecipeImport() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (pasteRecipeImportMode.value === 'replace') {
|
if (pasteRecipeImportMode.value === 'replace') {
|
||||||
editForm.herbs = resolved
|
const auxKept = editForm.herbs.filter((h) => normalizeFormulaType(h.formula_type) === '辅方')
|
||||||
|
editForm.herbs = [...auxKept, ...resolved]
|
||||||
} else {
|
} else {
|
||||||
editForm.herbs.push(...resolved)
|
editForm.herbs.push(...resolved)
|
||||||
}
|
}
|
||||||
@@ -3306,11 +3641,12 @@ async function handlePasteRecipeImport() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加药材
|
// 添加药材(主方 / 辅方)
|
||||||
const addHerb = () => {
|
const addHerb = (formulaType: FormulaType = '主方') => {
|
||||||
editForm.herbs.push({
|
editForm.herbs.push({
|
||||||
name: '',
|
name: '',
|
||||||
dosage: 0
|
dosage: 0,
|
||||||
|
formula_type: formulaType
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3360,6 +3696,8 @@ const resetForm = () => {
|
|||||||
editForm.is_system_auto = 0
|
editForm.is_system_auto = 0
|
||||||
editForm.business_prescription_audit_rejected = 0
|
editForm.business_prescription_audit_rejected = 0
|
||||||
editForm.business_prescription_audit_remark = ''
|
editForm.business_prescription_audit_remark = ''
|
||||||
|
editForm.times_per_day = 2
|
||||||
|
Object.assign(editForm.aux_usage, defaultAuxUsage('浓缩水丸'))
|
||||||
clearRxLinkedOrderHint()
|
clearRxLinkedOrderHint()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3458,7 +3796,7 @@ const handleEdit = async (row: any) => {
|
|||||||
editForm.pulse = src.pulse || ''
|
editForm.pulse = src.pulse || ''
|
||||||
editForm.pulse_condition = src.pulse_condition || ''
|
editForm.pulse_condition = src.pulse_condition || ''
|
||||||
editForm.clinical_diagnosis = src.clinical_diagnosis || ''
|
editForm.clinical_diagnosis = src.clinical_diagnosis || ''
|
||||||
editForm.herbs = src.herbs ? JSON.parse(JSON.stringify(src.herbs)) : []
|
editForm.herbs = src.herbs ? (src.herbs as any[]).map((h) => normalizeHerbRow(h)) : []
|
||||||
editForm.dose_count = src.dose_count ?? 7
|
editForm.dose_count = src.dose_count ?? 7
|
||||||
editForm.dose_unit = src.dose_unit || '剂'
|
editForm.dose_unit = src.dose_unit || '剂'
|
||||||
editForm.usage_days = src.usage_days ?? 7
|
editForm.usage_days = src.usage_days ?? 7
|
||||||
@@ -3483,6 +3821,10 @@ const handleEdit = async (row: any) => {
|
|||||||
editForm.is_system_auto = Number(src.is_system_auto) === 1 ? 1 : 0
|
editForm.is_system_auto = Number(src.is_system_auto) === 1 ? 1 : 0
|
||||||
editForm.business_prescription_audit_rejected = Number(src.business_prescription_audit_rejected) === 1 ? 1 : 0
|
editForm.business_prescription_audit_rejected = Number(src.business_prescription_audit_rejected) === 1 ? 1 : 0
|
||||||
editForm.business_prescription_audit_remark = String(src.business_prescription_audit_remark || '')
|
editForm.business_prescription_audit_remark = String(src.business_prescription_audit_remark || '')
|
||||||
|
Object.assign(
|
||||||
|
editForm.aux_usage,
|
||||||
|
normalizeAuxUsageForm(src.aux_usage, editForm.prescription_type)
|
||||||
|
)
|
||||||
|
|
||||||
// 数据加载完成,重新启用watch
|
// 数据加载完成,重新启用watch
|
||||||
// 使用 setTimeout 确保所有数据都已经渲染完成
|
// 使用 setTimeout 确保所有数据都已经渲染完成
|
||||||
@@ -3517,12 +3859,13 @@ const handleSubmit = async () => {
|
|||||||
|
|
||||||
for (let i = 0; i < editForm.herbs.length; i++) {
|
for (let i = 0; i < editForm.herbs.length; i++) {
|
||||||
const herb = editForm.herbs[i]
|
const herb = editForm.herbs[i]
|
||||||
|
const label = herbValidationLabel(i)
|
||||||
if (!herb.name || !herb.name.trim()) {
|
if (!herb.name || !herb.name.trim()) {
|
||||||
feedback.msgError(`第${i + 1}味药材名称不能为空`)
|
feedback.msgError(`${label}药材名称不能为空`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!herb.dosage || herb.dosage <= 0) {
|
if (!herb.dosage || herb.dosage <= 0) {
|
||||||
feedback.msgError(`第${i + 1}味药材剂量必须大于0`)
|
feedback.msgError(`${label}药材剂量必须大于0`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3946,6 +4289,38 @@ onMounted(async () => {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rx-herb-section-label {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #409eff;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rx-herb-section-label--aux {
|
||||||
|
color: #e6a23c;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.herb-formula-block__head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-formula-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 8px 0 4px;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-formula-title--aux {
|
||||||
|
color: #e6a23c;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.rx-herb-cell {
|
.rx-herb-cell {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 64px;
|
grid-template-columns: 1fr 64px;
|
||||||
|
|||||||
@@ -11,6 +11,13 @@
|
|||||||
@keyup.enter="resetPage"
|
@keyup.enter="resetPage"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item class="w-[200px]" label="处方类型">
|
||||||
|
<el-select v-model="formData.formula_type" placeholder="全部" clearable>
|
||||||
|
<el-option label="全部" :value="''" />
|
||||||
|
<el-option label="主方" value="主方" />
|
||||||
|
<el-option label="辅方" value="辅方" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item class="w-[200px]" label="是否公开">
|
<el-form-item class="w-[200px]" label="是否公开">
|
||||||
<el-select v-model="formData.is_public" placeholder="全部" clearable>
|
<el-select v-model="formData.is_public" placeholder="全部" clearable>
|
||||||
<el-option label="全部" :value="''" />
|
<el-option label="全部" :value="''" />
|
||||||
@@ -37,6 +44,13 @@
|
|||||||
<el-table :data="pager.lists" size="large">
|
<el-table :data="pager.lists" size="large">
|
||||||
<el-table-column label="ID" prop="id" min-width="60" />
|
<el-table-column label="ID" prop="id" min-width="60" />
|
||||||
<el-table-column label="处方名称" prop="prescription_name" min-width="200" show-overflow-tooltip />
|
<el-table-column label="处方名称" prop="prescription_name" min-width="200" show-overflow-tooltip />
|
||||||
|
<el-table-column label="处方类型" min-width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.formula_type === '辅方' ? 'warning' : 'primary'" size="small">
|
||||||
|
{{ row.formula_type || '主方' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="药材数量" min-width="100">
|
<el-table-column label="药材数量" min-width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ row.herbs?.length || 0 }}味
|
{{ row.herbs?.length || 0 }}味
|
||||||
@@ -115,6 +129,13 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="处方类型" prop="formula_type">
|
||||||
|
<el-radio-group v-model="editForm.formula_type">
|
||||||
|
<el-radio value="主方">主方</el-radio>
|
||||||
|
<el-radio value="辅方">辅方</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="药材配方" prop="herbs" required>
|
<el-form-item label="药材配方" prop="herbs" required>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<el-button
|
<el-button
|
||||||
@@ -200,6 +221,7 @@ import MedicineNameSelect from '@/components/medicine-name-select/index.vue'
|
|||||||
// 表单数据
|
// 表单数据
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
prescription_name: '',
|
prescription_name: '',
|
||||||
|
formula_type: '',
|
||||||
is_public: ''
|
is_public: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -212,6 +234,7 @@ const formRef = ref<FormInstance>()
|
|||||||
const editForm = reactive({
|
const editForm = reactive({
|
||||||
id: 0,
|
id: 0,
|
||||||
prescription_name: '',
|
prescription_name: '',
|
||||||
|
formula_type: '主方',
|
||||||
herbs: [] as Array<{ name: string; dosage: number }>,
|
herbs: [] as Array<{ name: string; dosage: number }>,
|
||||||
is_public: 0
|
is_public: 0
|
||||||
})
|
})
|
||||||
@@ -267,6 +290,7 @@ const removeHerb = (index: number) => {
|
|||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
editForm.id = 0
|
editForm.id = 0
|
||||||
editForm.prescription_name = ''
|
editForm.prescription_name = ''
|
||||||
|
editForm.formula_type = '主方'
|
||||||
editForm.herbs = []
|
editForm.herbs = []
|
||||||
editForm.is_public = 0
|
editForm.is_public = 0
|
||||||
}
|
}
|
||||||
@@ -283,6 +307,7 @@ const handleView = (row: any) => {
|
|||||||
editMode.value = 'view'
|
editMode.value = 'view'
|
||||||
editForm.id = row.id
|
editForm.id = row.id
|
||||||
editForm.prescription_name = row.prescription_name || ''
|
editForm.prescription_name = row.prescription_name || ''
|
||||||
|
editForm.formula_type = row.formula_type || '主方'
|
||||||
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
||||||
editForm.is_public = row.is_public ?? 0
|
editForm.is_public = row.is_public ?? 0
|
||||||
showEdit.value = true
|
showEdit.value = true
|
||||||
@@ -293,6 +318,7 @@ const handleEdit = (row: any) => {
|
|||||||
editMode.value = 'edit'
|
editMode.value = 'edit'
|
||||||
editForm.id = row.id
|
editForm.id = row.id
|
||||||
editForm.prescription_name = row.prescription_name || ''
|
editForm.prescription_name = row.prescription_name || ''
|
||||||
|
editForm.formula_type = row.formula_type || '主方'
|
||||||
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
editForm.herbs = row.herbs ? JSON.parse(JSON.stringify(row.herbs)) : []
|
||||||
editForm.is_public = row.is_public ?? 0
|
editForm.is_public = row.is_public ?? 0
|
||||||
showEdit.value = true
|
showEdit.value = true
|
||||||
|
|||||||
@@ -1236,6 +1236,7 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-2 flex-wrap">
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
<span class="font-medium text-[15px]">物流轨迹</span>
|
<span class="font-medium text-[15px]">物流轨迹</span>
|
||||||
|
<span class="text-xs text-gray-400 font-mono">{{ detailData.tracking_number }}</span>
|
||||||
<el-tag
|
<el-tag
|
||||||
v-if="detailLogisticsUrgent.show"
|
v-if="detailLogisticsUrgent.show"
|
||||||
type="danger"
|
type="danger"
|
||||||
@@ -2375,17 +2376,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rx-herbs">
|
<div class="rx-herbs">
|
||||||
<div v-for="(h, i) in slipHerbsList" :key="i" class="rx-herb-cell">
|
<template v-if="prescriptionTabType === 'internal'">
|
||||||
<span class="rx-herb-name" v-if="prescriptionTabType === 'internal'">{{ h.name }} ({{ h.dosage }}克)</span>
|
<template v-if="slipMainHerbs.length">
|
||||||
<span class="rx-herb-name" v-else>{{ h.name }}</span>
|
<div class="rx-herb-section-label">主方</div>
|
||||||
<span class="rx-herb-total" v-if="prescriptionTabType === 'internal'">{{ rxHerbTotal(h.dosage) }}克</span>
|
<div
|
||||||
<span class="rx-herb-total" v-else>{{ h.dosage }}克</span>
|
v-for="(h, i) in slipMainHerbs"
|
||||||
|
:key="'main-' + i"
|
||||||
|
class="rx-herb-cell"
|
||||||
|
>
|
||||||
|
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }}克)</span>
|
||||||
|
<span class="rx-herb-total">{{ rxHerbTotal(h.dosage) }}克</span>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="slipAuxHerbs.length">
|
||||||
|
<div class="rx-herb-section-label rx-herb-section-label--aux">辅方</div>
|
||||||
|
<div
|
||||||
|
v-for="(h, i) in slipAuxHerbs"
|
||||||
|
:key="'aux-' + i"
|
||||||
|
class="rx-herb-cell"
|
||||||
|
>
|
||||||
|
<span class="rx-herb-name">{{ h.name }} ({{ h.dosage }}克)</span>
|
||||||
|
<span class="rx-herb-total">{{ rxHerbTotal(h.dosage) }}克</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div v-for="(h, i) in slipHerbsList" :key="i" class="rx-herb-cell">
|
||||||
|
<span class="rx-herb-name">{{ h.name }}</span>
|
||||||
|
<span class="rx-herb-total">{{ h.dosage }}克</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rx-text">
|
<div class="rx-text">
|
||||||
<p>服法:{{ rxUsageText }}</p>
|
<p v-if="prescriptionTabType === 'internal'">主方服法:{{ rxUsageText }}</p>
|
||||||
|
<p v-else>服法:{{ rxUsageText }}</p>
|
||||||
|
<p v-if="prescriptionTabType === 'internal' && rxAuxUsageText">辅方服法:{{ rxAuxUsageText }}</p>
|
||||||
<p v-if="rxAdviceText">医嘱:{{ rxAdviceText }}</p>
|
<p v-if="rxAdviceText">医嘱:{{ rxAdviceText }}</p>
|
||||||
<p v-if="rxRemarkText">备注:{{ rxRemarkText }}</p>
|
<p v-if="rxRemarkText">备注:{{ rxRemarkText }}</p>
|
||||||
<p v-if="rxPharmacyRemarkText" class="rx-text-warn">
|
<p v-if="rxPharmacyRemarkText" class="rx-text-warn">
|
||||||
@@ -3777,6 +3804,40 @@ const logisticsTraceLoading = ref(false)
|
|||||||
const logisticsTracePayload = ref<Record<string, any> | null>(null)
|
const logisticsTracePayload = ref<Record<string, any> | null>(null)
|
||||||
/** 顺丰/快递100:与运单一致的收件手机后四位(可覆盖订单收货手机) */
|
/** 顺丰/快递100:与运单一致的收件手机后四位(可覆盖订单收货手机) */
|
||||||
const logisticsTracePhoneTail = ref('')
|
const logisticsTracePhoneTail = ref('')
|
||||||
|
/** 物流轨迹请求序号:同手机多运单或快速切换订单时,丢弃过期的异步响应 */
|
||||||
|
let logisticsTraceRequestSeq = 0
|
||||||
|
|
||||||
|
type LogisticsTraceRequestContext = {
|
||||||
|
requestSeq: number
|
||||||
|
orderId: number
|
||||||
|
trackingNumber: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function bumpLogisticsTraceRequestToken() {
|
||||||
|
logisticsTraceRequestSeq += 1
|
||||||
|
logisticsTracePayload.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCurrentLogisticsTraceContext(ctx: LogisticsTraceRequestContext): boolean {
|
||||||
|
if (ctx.requestSeq !== logisticsTraceRequestSeq) return false
|
||||||
|
if (Number(detailData.value?.id) !== ctx.orderId) return false
|
||||||
|
return String(detailData.value?.tracking_number || '').trim() === ctx.trackingNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLogisticsTracePayload(
|
||||||
|
res: unknown,
|
||||||
|
expectedTrackingNumber: string,
|
||||||
|
expectedOrderId: number
|
||||||
|
): Record<string, any> | null {
|
||||||
|
const raw = (res as { data?: unknown })?.data ?? res
|
||||||
|
if (!raw || typeof raw !== 'object') return null
|
||||||
|
const payload = raw as Record<string, any>
|
||||||
|
const respNum = String(payload.tracking_number || '').trim()
|
||||||
|
if (respNum && respNum !== expectedTrackingNumber) return null
|
||||||
|
const respOrderId = Number(payload.order_id)
|
||||||
|
if (respOrderId > 0 && respOrderId !== expectedOrderId) return null
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
const detailLogs = ref<any[]>([])
|
const detailLogs = ref<any[]>([])
|
||||||
const updateAmountVisible = ref(false)
|
const updateAmountVisible = ref(false)
|
||||||
@@ -3942,25 +4003,34 @@ function expressCompanyLabel(v: unknown) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchLogisticsTrace() {
|
async function fetchLogisticsTrace() {
|
||||||
const id = Number(detailData.value?.id)
|
const orderId = Number(detailData.value?.id)
|
||||||
if (!id) return
|
const trackingNumber = String(detailData.value?.tracking_number || '').trim()
|
||||||
|
if (!orderId || !trackingNumber) return
|
||||||
|
|
||||||
|
const requestSeq = ++logisticsTraceRequestSeq
|
||||||
|
const ctx: LogisticsTraceRequestContext = { requestSeq, orderId, trackingNumber }
|
||||||
|
|
||||||
logisticsTraceLoading.value = true
|
logisticsTraceLoading.value = true
|
||||||
try {
|
try {
|
||||||
const digits = String(logisticsTracePhoneTail.value || '').replace(/\D/g, '')
|
const digits = String(logisticsTracePhoneTail.value || '').replace(/\D/g, '')
|
||||||
const params: { id: number; express_company?: string; phone_tail?: string } = {
|
const params: { id: number; express_company?: string; phone_tail?: string } = {
|
||||||
id,
|
id: orderId,
|
||||||
express_company: detailLogisticsExpress.value
|
express_company: detailLogisticsExpress.value
|
||||||
}
|
}
|
||||||
if (digits.length >= 4) {
|
if (digits.length >= 4) {
|
||||||
params.phone_tail = digits
|
params.phone_tail = digits
|
||||||
}
|
}
|
||||||
const res: any = await prescriptionOrderLogisticsTrace(params)
|
const res: any = await prescriptionOrderLogisticsTrace(params)
|
||||||
logisticsTracePayload.value = (res?.data ?? res) as Record<string, any>
|
if (!isCurrentLogisticsTraceContext(ctx)) return
|
||||||
|
logisticsTracePayload.value = parseLogisticsTracePayload(res, trackingNumber, orderId)
|
||||||
} catch {
|
} catch {
|
||||||
|
if (!isCurrentLogisticsTraceContext(ctx)) return
|
||||||
logisticsTracePayload.value = null
|
logisticsTracePayload.value = null
|
||||||
} finally {
|
} finally {
|
||||||
|
if (isCurrentLogisticsTraceContext(ctx)) {
|
||||||
logisticsTraceLoading.value = false
|
logisticsTraceLoading.value = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const patchRxPatientVisible = ref(false)
|
const patchRxPatientVisible = ref(false)
|
||||||
@@ -4085,7 +4155,7 @@ async function openDetail(id: number) {
|
|||||||
// 修复 Bug:显式彻底清空缓存,防止前一次弹窗的数据残留
|
// 修复 Bug:显式彻底清空缓存,防止前一次弹窗的数据残留
|
||||||
detailData.value = null
|
detailData.value = null
|
||||||
detailUnlinkedPayOrders.value = []
|
detailUnlinkedPayOrders.value = []
|
||||||
logisticsTracePayload.value = null
|
bumpLogisticsTraceRequestToken()
|
||||||
detailLogisticsExpress.value = 'auto'
|
detailLogisticsExpress.value = 'auto'
|
||||||
logisticsTracePhoneTail.value = ''
|
logisticsTracePhoneTail.value = ''
|
||||||
detailLogs.value = []
|
detailLogs.value = []
|
||||||
@@ -4928,36 +4998,48 @@ const completeOrderDialogVisible = ref(false)
|
|||||||
const completeOrderId = ref(0)
|
const completeOrderId = ref(0)
|
||||||
const completeFulfillmentStatus = ref<number>(3)
|
const completeFulfillmentStatus = ref<number>(3)
|
||||||
const completeOrderSubmitting = ref(false)
|
const completeOrderSubmitting = ref(false)
|
||||||
|
let completeOrderLogisticsRequestSeq = 0
|
||||||
|
|
||||||
function onCompleteOrderDialogClosed() {
|
function onCompleteOrderDialogClosed() {
|
||||||
completeOrderId.value = 0
|
completeOrderId.value = 0
|
||||||
|
completeOrderLogisticsRequestSeq += 1
|
||||||
completeOrderLogisticsPayload.value = null
|
completeOrderLogisticsPayload.value = null
|
||||||
completeOrderLogisticsLoading.value = false
|
completeOrderLogisticsLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchCompleteOrderLogisticsForDialog(row: {
|
async function fetchCompleteOrderLogisticsForDialog(row: {
|
||||||
id: number
|
id: number
|
||||||
|
tracking_number?: unknown
|
||||||
express_company?: unknown
|
express_company?: unknown
|
||||||
recipient_phone?: unknown
|
recipient_phone?: unknown
|
||||||
}) {
|
}) {
|
||||||
|
const orderId = Number(row.id)
|
||||||
|
const trackingNumber = String(row.tracking_number || '').trim()
|
||||||
|
if (!orderId || !trackingNumber) return
|
||||||
|
|
||||||
|
const requestSeq = ++completeOrderLogisticsRequestSeq
|
||||||
completeOrderLogisticsLoading.value = true
|
completeOrderLogisticsLoading.value = true
|
||||||
completeOrderLogisticsPayload.value = null
|
completeOrderLogisticsPayload.value = null
|
||||||
try {
|
try {
|
||||||
const digits = String(row.recipient_phone || '').replace(/\D/g, '')
|
const digits = String(row.recipient_phone || '').replace(/\D/g, '')
|
||||||
const params: { id: number; express_company?: string; phone_tail?: string } = {
|
const params: { id: number; express_company?: string; phone_tail?: string } = {
|
||||||
id: row.id,
|
id: orderId,
|
||||||
express_company: String(row.express_company || 'auto') || 'auto'
|
express_company: String(row.express_company || 'auto') || 'auto'
|
||||||
}
|
}
|
||||||
if (digits.length >= 4) {
|
if (digits.length >= 4) {
|
||||||
params.phone_tail = digits
|
params.phone_tail = digits
|
||||||
}
|
}
|
||||||
const res: any = await prescriptionOrderLogisticsTrace(params)
|
const res: any = await prescriptionOrderLogisticsTrace(params)
|
||||||
completeOrderLogisticsPayload.value = (res?.data ?? res) as Record<string, any>
|
if (requestSeq !== completeOrderLogisticsRequestSeq || completeOrderId.value !== orderId) return
|
||||||
|
completeOrderLogisticsPayload.value = parseLogisticsTracePayload(res, trackingNumber, orderId)
|
||||||
} catch {
|
} catch {
|
||||||
|
if (requestSeq !== completeOrderLogisticsRequestSeq || completeOrderId.value !== orderId) return
|
||||||
completeOrderLogisticsPayload.value = null
|
completeOrderLogisticsPayload.value = null
|
||||||
} finally {
|
} finally {
|
||||||
|
if (requestSeq === completeOrderLogisticsRequestSeq && completeOrderId.value === orderId) {
|
||||||
completeOrderLogisticsLoading.value = false
|
completeOrderLogisticsLoading.value = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitCompleteOrder() {
|
async function submitCompleteOrder() {
|
||||||
@@ -5219,12 +5301,116 @@ const prescriptionTabType = ref('internal')
|
|||||||
const prescriptionSlipPrintRef = ref<HTMLElement | null>(null)
|
const prescriptionSlipPrintRef = ref<HTMLElement | null>(null)
|
||||||
const prescriptionSlipExporting = ref(false)
|
const prescriptionSlipExporting = ref(false)
|
||||||
|
|
||||||
|
type SlipFormulaType = '主方' | '辅方'
|
||||||
|
|
||||||
|
type SlipAuxUsageForm = {
|
||||||
|
dosage_amount?: number
|
||||||
|
dosage_bag_count: number
|
||||||
|
need_decoction: boolean
|
||||||
|
bags_per_dose: number
|
||||||
|
times_per_day: number
|
||||||
|
usage_days: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSlipFormulaType(v: unknown): SlipFormulaType {
|
||||||
|
return v === '辅方' ? '辅方' : '主方'
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultSlipAuxUsage(prescriptionType = '浓缩水丸'): SlipAuxUsageForm {
|
||||||
|
if (prescriptionType === '饮片') {
|
||||||
|
return {
|
||||||
|
dosage_amount: 50,
|
||||||
|
dosage_bag_count: 1,
|
||||||
|
need_decoction: false,
|
||||||
|
bags_per_dose: 1,
|
||||||
|
times_per_day: 3,
|
||||||
|
usage_days: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prescriptionType === '浓缩水丸') {
|
||||||
|
return {
|
||||||
|
dosage_amount: 5,
|
||||||
|
dosage_bag_count: 1,
|
||||||
|
need_decoction: false,
|
||||||
|
bags_per_dose: 1,
|
||||||
|
times_per_day: 3,
|
||||||
|
usage_days: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
dosage_amount: 1,
|
||||||
|
dosage_bag_count: 1,
|
||||||
|
need_decoction: false,
|
||||||
|
bags_per_dose: 1,
|
||||||
|
times_per_day: 3,
|
||||||
|
usage_days: 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSlipAuxUsageForm(raw: unknown, prescriptionType: string): SlipAuxUsageForm {
|
||||||
|
const base = defaultSlipAuxUsage(prescriptionType)
|
||||||
|
if (!raw || typeof raw !== 'object') return { ...base }
|
||||||
|
const o = raw as Record<string, unknown>
|
||||||
|
return {
|
||||||
|
dosage_amount:
|
||||||
|
o.dosage_amount !== null && o.dosage_amount !== undefined && o.dosage_amount !== ''
|
||||||
|
? Number(o.dosage_amount)
|
||||||
|
: base.dosage_amount,
|
||||||
|
dosage_bag_count: o.dosage_bag_count != null ? Number(o.dosage_bag_count) || 1 : base.dosage_bag_count,
|
||||||
|
need_decoction: o.need_decoction === 1 || o.need_decoction === true,
|
||||||
|
bags_per_dose: o.bags_per_dose != null ? Number(o.bags_per_dose) || 1 : base.bags_per_dose,
|
||||||
|
times_per_day: o.times_per_day != null ? Number(o.times_per_day) || 3 : base.times_per_day,
|
||||||
|
usage_days: o.usage_days != null ? Number(o.usage_days) || 7 : base.usage_days
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSlipUsageSegmentText(
|
||||||
|
usage: {
|
||||||
|
prescription_type?: string
|
||||||
|
dosage_amount?: number | null
|
||||||
|
dosage_unit?: string
|
||||||
|
dosage_bag_count?: number
|
||||||
|
times_per_day?: number
|
||||||
|
usage_way?: string
|
||||||
|
usage_time?: string
|
||||||
|
},
|
||||||
|
fallbackWay?: string,
|
||||||
|
fallbackTime?: string
|
||||||
|
): string {
|
||||||
|
const pt = usage.prescription_type || '浓缩水丸'
|
||||||
|
const times = Number(usage.times_per_day) > 0 ? Number(usage.times_per_day) : 3
|
||||||
|
const amount = usage.dosage_amount != null ? Number(usage.dosage_amount) : 10
|
||||||
|
const unit = usage.dosage_unit || (pt === '饮片' ? 'ml' : 'g')
|
||||||
|
const usageWay = usage.usage_way || fallbackWay || '温水送服'
|
||||||
|
const usageTime = usage.usage_time || fallbackTime || ''
|
||||||
|
const seg: string[] = []
|
||||||
|
seg.push(`每天${times}次`)
|
||||||
|
if (pt === '浓缩水丸') {
|
||||||
|
const bags = Number(usage.dosage_bag_count) > 0 ? Number(usage.dosage_bag_count) : 1
|
||||||
|
seg.push(`一次${bags}袋`)
|
||||||
|
seg.push(`每袋${amount}${unit}`)
|
||||||
|
} else {
|
||||||
|
seg.push(`一次${amount}${unit}`)
|
||||||
|
}
|
||||||
|
seg.push(usageWay)
|
||||||
|
if (usageTime) seg.push(usageTime)
|
||||||
|
return seg.join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
// 处方单辅助函数
|
// 处方单辅助函数
|
||||||
const slipHerbsList = computed(() => {
|
const slipHerbsList = computed(() => {
|
||||||
const h = prescriptionViewData.value?.herbs
|
const h = prescriptionViewData.value?.herbs
|
||||||
return Array.isArray(h) ? h : []
|
return Array.isArray(h) ? h : []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const slipMainHerbs = computed(() =>
|
||||||
|
slipHerbsList.value.filter((h: any) => normalizeSlipFormulaType(h?.formula_type) === '主方')
|
||||||
|
)
|
||||||
|
|
||||||
|
const slipAuxHerbs = computed(() =>
|
||||||
|
slipHerbsList.value.filter((h: any) => normalizeSlipFormulaType(h?.formula_type) === '辅方')
|
||||||
|
)
|
||||||
|
|
||||||
const slipDietaryText = computed(() => {
|
const slipDietaryText = computed(() => {
|
||||||
const d = prescriptionViewData.value?.dietary_taboo
|
const d = prescriptionViewData.value?.dietary_taboo
|
||||||
if (Array.isArray(d)) return d.filter(Boolean).join('、')
|
if (Array.isArray(d)) return d.filter(Boolean).join('、')
|
||||||
@@ -5351,23 +5537,26 @@ const rxUsageText = computed(() => {
|
|||||||
const v = prescriptionViewData.value as any
|
const v = prescriptionViewData.value as any
|
||||||
if (!v) return '—'
|
if (!v) return '—'
|
||||||
if (v.usage_text) return v.usage_text
|
if (v.usage_text) return v.usage_text
|
||||||
const times = Number(v.times_per_day) > 0 ? Number(v.times_per_day) : 3
|
return buildSlipUsageSegmentText(v)
|
||||||
const amount = v.dosage_amount != null ? Number(v.dosage_amount) : 10
|
})
|
||||||
const unit = v.dosage_unit || 'g'
|
|
||||||
const usageWay = v.usage_way || '温水送服'
|
const rxAuxUsageText = computed(() => {
|
||||||
const usageTime = v.usage_time || ''
|
const v = prescriptionViewData.value as any
|
||||||
const seg: string[] = []
|
if (!v || !slipAuxHerbs.value.length) return ''
|
||||||
seg.push(`每天${times}次`)
|
const aux = normalizeSlipAuxUsageForm(v.aux_usage, v.prescription_type || '浓缩水丸')
|
||||||
if ((v.prescription_type || '浓缩水丸') === '浓缩水丸') {
|
return buildSlipUsageSegmentText(
|
||||||
const bags = Number(v.dosage_bag_count) > 0 ? Number(v.dosage_bag_count) : 1
|
{
|
||||||
seg.push(`一次${bags}袋`)
|
prescription_type: v.prescription_type,
|
||||||
seg.push(`每袋${amount}${unit}`)
|
dosage_amount: aux.dosage_amount,
|
||||||
} else {
|
dosage_unit: v.dosage_unit,
|
||||||
seg.push(`一次${amount}${unit}`)
|
dosage_bag_count: aux.dosage_bag_count,
|
||||||
}
|
times_per_day: aux.times_per_day,
|
||||||
seg.push(usageWay)
|
usage_way: v.usage_way,
|
||||||
if (usageTime) seg.push(usageTime)
|
usage_time: v.usage_time
|
||||||
return seg.join(', ')
|
},
|
||||||
|
v.usage_way,
|
||||||
|
v.usage_time
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 出丸:优先用 slipPillGrams(每袋用量×袋数×每天次数×服用天数),否则按药材总量×剂数 */
|
/** 出丸:优先用 slipPillGrams(每袋用量×袋数×每天次数×服用天数),否则按药材总量×剂数 */
|
||||||
@@ -6170,6 +6359,19 @@ async function downloadPrescriptionSlipPdf() {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rx-herb-section-label {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #409eff;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rx-herb-section-label--aux {
|
||||||
|
color: #e6a23c;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.rx-herb-cell {
|
.rx-herb-cell {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 64px;
|
grid-template-columns: 1fr 64px;
|
||||||
|
|||||||
@@ -3406,6 +3406,40 @@ const logisticsTraceLoading = ref(false)
|
|||||||
const logisticsTracePayload = ref<Record<string, any> | null>(null)
|
const logisticsTracePayload = ref<Record<string, any> | null>(null)
|
||||||
/** 顺丰/快递100:与运单一致的收件手机后四位(可覆盖订单收货手机) */
|
/** 顺丰/快递100:与运单一致的收件手机后四位(可覆盖订单收货手机) */
|
||||||
const logisticsTracePhoneTail = ref('')
|
const logisticsTracePhoneTail = ref('')
|
||||||
|
/** 物流轨迹请求序号:同手机多运单或快速切换订单时,丢弃过期的异步响应 */
|
||||||
|
let logisticsTraceRequestSeq = 0
|
||||||
|
|
||||||
|
type LogisticsTraceRequestContext = {
|
||||||
|
requestSeq: number
|
||||||
|
orderId: number
|
||||||
|
trackingNumber: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function bumpLogisticsTraceRequestToken() {
|
||||||
|
logisticsTraceRequestSeq += 1
|
||||||
|
logisticsTracePayload.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCurrentLogisticsTraceContext(ctx: LogisticsTraceRequestContext): boolean {
|
||||||
|
if (ctx.requestSeq !== logisticsTraceRequestSeq) return false
|
||||||
|
if (Number(detailData.value?.id) !== ctx.orderId) return false
|
||||||
|
return String(detailData.value?.tracking_number || '').trim() === ctx.trackingNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLogisticsTracePayload(
|
||||||
|
res: unknown,
|
||||||
|
expectedTrackingNumber: string,
|
||||||
|
expectedOrderId: number
|
||||||
|
): Record<string, any> | null {
|
||||||
|
const raw = (res as { data?: unknown })?.data ?? res
|
||||||
|
if (!raw || typeof raw !== 'object') return null
|
||||||
|
const payload = raw as Record<string, any>
|
||||||
|
const respNum = String(payload.tracking_number || '').trim()
|
||||||
|
if (respNum && respNum !== expectedTrackingNumber) return null
|
||||||
|
const respOrderId = Number(payload.order_id)
|
||||||
|
if (respOrderId > 0 && respOrderId !== expectedOrderId) return null
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
const detailLogs = ref<any[]>([])
|
const detailLogs = ref<any[]>([])
|
||||||
const updateAmountVisible = ref(false)
|
const updateAmountVisible = ref(false)
|
||||||
@@ -3500,25 +3534,34 @@ function expressCompanyLabel(v: unknown) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchLogisticsTrace() {
|
async function fetchLogisticsTrace() {
|
||||||
const id = Number(detailData.value?.id)
|
const orderId = Number(detailData.value?.id)
|
||||||
if (!id) return
|
const trackingNumber = String(detailData.value?.tracking_number || '').trim()
|
||||||
|
if (!orderId || !trackingNumber) return
|
||||||
|
|
||||||
|
const requestSeq = ++logisticsTraceRequestSeq
|
||||||
|
const ctx: LogisticsTraceRequestContext = { requestSeq, orderId, trackingNumber }
|
||||||
|
|
||||||
logisticsTraceLoading.value = true
|
logisticsTraceLoading.value = true
|
||||||
try {
|
try {
|
||||||
const digits = String(logisticsTracePhoneTail.value || '').replace(/\D/g, '')
|
const digits = String(logisticsTracePhoneTail.value || '').replace(/\D/g, '')
|
||||||
const params: { id: number; express_company?: string; phone_tail?: string } = {
|
const params: { id: number; express_company?: string; phone_tail?: string } = {
|
||||||
id,
|
id: orderId,
|
||||||
express_company: detailLogisticsExpress.value
|
express_company: detailLogisticsExpress.value
|
||||||
}
|
}
|
||||||
if (digits.length >= 4) {
|
if (digits.length >= 4) {
|
||||||
params.phone_tail = digits
|
params.phone_tail = digits
|
||||||
}
|
}
|
||||||
const res: any = await prescriptionOrderLogisticsTrace(params)
|
const res: any = await prescriptionOrderLogisticsTrace(params)
|
||||||
logisticsTracePayload.value = (res?.data ?? res) as Record<string, any>
|
if (!isCurrentLogisticsTraceContext(ctx)) return
|
||||||
|
logisticsTracePayload.value = parseLogisticsTracePayload(res, trackingNumber, orderId)
|
||||||
} catch {
|
} catch {
|
||||||
|
if (!isCurrentLogisticsTraceContext(ctx)) return
|
||||||
logisticsTracePayload.value = null
|
logisticsTracePayload.value = null
|
||||||
} finally {
|
} finally {
|
||||||
|
if (isCurrentLogisticsTraceContext(ctx)) {
|
||||||
logisticsTraceLoading.value = false
|
logisticsTraceLoading.value = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const patchRxPatientVisible = ref(false)
|
const patchRxPatientVisible = ref(false)
|
||||||
@@ -3643,7 +3686,7 @@ async function openDetail(id: number) {
|
|||||||
// 修复 Bug:显式彻底清空缓存,防止前一次弹窗的数据残留
|
// 修复 Bug:显式彻底清空缓存,防止前一次弹窗的数据残留
|
||||||
detailData.value = null
|
detailData.value = null
|
||||||
detailUnlinkedPayOrders.value = []
|
detailUnlinkedPayOrders.value = []
|
||||||
logisticsTracePayload.value = null
|
bumpLogisticsTraceRequestToken()
|
||||||
detailLogisticsExpress.value = 'auto'
|
detailLogisticsExpress.value = 'auto'
|
||||||
logisticsTracePhoneTail.value = ''
|
logisticsTracePhoneTail.value = ''
|
||||||
detailLogs.value = []
|
detailLogs.value = []
|
||||||
@@ -3659,7 +3702,7 @@ async function openDetail(id: number) {
|
|||||||
const dig = String(d.recipient_phone || '').replace(/\D/g, '')
|
const dig = String(d.recipient_phone || '').replace(/\D/g, '')
|
||||||
logisticsTracePhoneTail.value = dig.length >= 4 ? dig : ''
|
logisticsTracePhoneTail.value = dig.length >= 4 ? dig : ''
|
||||||
if (String(d.tracking_number || '').trim()) {
|
if (String(d.tracking_number || '').trim()) {
|
||||||
fetchLogisticsTrace()
|
void fetchLogisticsTrace()
|
||||||
}
|
}
|
||||||
fetchLogs(id)
|
fetchLogs(id)
|
||||||
|
|
||||||
|
|||||||
@@ -132,9 +132,42 @@ class AuthMiddleware
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处方库列表:消费者开方页/处方库页导入共用,复用开方或处方库菜单权限
|
||||||
|
if ($accessUri === 'tcm.prescriptionlibrary/lists'
|
||||||
|
&& $this->matchPrescriptionLibraryListsPermission($adminUris)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处方库 lists:与开方、处方库维护菜单权限互通(避免开方页「从处方库导入」403)
|
||||||
|
*/
|
||||||
|
private function matchPrescriptionLibraryListsPermission(array $adminUris): bool
|
||||||
|
{
|
||||||
|
$aliases = [
|
||||||
|
'tcm.prescriptionlibrary/lists',
|
||||||
|
'tcm.prescription/lists',
|
||||||
|
'tcm.prescription/add',
|
||||||
|
'tcm.prescription/edit',
|
||||||
|
'tcm.prescription/detail',
|
||||||
|
'cf.prescription/lists',
|
||||||
|
'cf.prescription/add',
|
||||||
|
'cf.prescription/edit',
|
||||||
|
'cf.prescription/read',
|
||||||
|
'cf.prescription/del',
|
||||||
|
'cf.prescription/audit',
|
||||||
|
'wcf.prescription/lists',
|
||||||
|
'wcf.prescription/read',
|
||||||
|
'wcf.prescription/add',
|
||||||
|
'wcf.prescription/edit',
|
||||||
|
'wcf.prescription/delete',
|
||||||
|
];
|
||||||
|
|
||||||
|
return count(array_intersect($aliases, $adminUris)) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 面诊进度专用:auth.admin/lists、doctor.appointment/lists + progress_board=1,不校验菜单权限
|
* 面诊进度专用:auth.admin/lists、doctor.appointment/lists + progress_board=1,不校验菜单权限
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class PrescriptionLibraryLists extends BaseAdminDataLists implements ListsSearch
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'%like%' => ['prescription_name'],
|
'%like%' => ['prescription_name'],
|
||||||
'=' => ['is_public', 'creator_id']
|
'=' => ['is_public', 'creator_id', 'formula_type']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +34,16 @@ class PrescriptionLibraryLists extends BaseAdminDataLists implements ListsSearch
|
|||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 开方页导入专用参数(勿与搜索项 creator_id 混用,否则无法 OR 出他人公开模板)
|
||||||
|
$prescribingCreatorId = (int) ($this->params['prescribing_creator_id'] ?? 0);
|
||||||
|
if ($prescribingCreatorId > 0
|
||||||
|
&& PrescriptionLibraryLogic::canListLibraryForCreator($this->adminId, $this->adminInfo, $prescribingCreatorId)) {
|
||||||
|
return $query->where(function ($q) use ($prescribingCreatorId) {
|
||||||
|
$q->where('creator_id', $prescribingCreatorId)
|
||||||
|
->whereOr('is_public', 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return $query->where(function ($q) {
|
return $query->where(function ($q) {
|
||||||
$q->where('creator_id', $this->adminId)
|
$q->where('creator_id', $this->adminId)
|
||||||
->whereOr('is_public', 1);
|
->whereOr('is_public', 1);
|
||||||
@@ -46,7 +56,7 @@ class PrescriptionLibraryLists extends BaseAdminDataLists implements ListsSearch
|
|||||||
public function lists(): array
|
public function lists(): array
|
||||||
{
|
{
|
||||||
$field = [
|
$field = [
|
||||||
'id', 'prescription_name', 'herbs', 'is_public',
|
'id', 'prescription_name', 'formula_type', 'herbs', 'is_public',
|
||||||
'creator_id', 'creator_name', 'create_time', 'update_time'
|
'creator_id', 'creator_name', 'create_time', 'update_time'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace app\adminapi\logic\tcm;
|
namespace app\adminapi\logic\tcm;
|
||||||
|
|
||||||
|
use app\common\cache\AdminAuthCache;
|
||||||
use app\common\logic\BaseLogic;
|
use app\common\logic\BaseLogic;
|
||||||
use app\common\model\auth\AdminRole;
|
use app\common\model\auth\AdminRole;
|
||||||
use app\common\model\tcm\PrescriptionLibrary;
|
use app\common\model\tcm\PrescriptionLibrary;
|
||||||
@@ -33,12 +34,64 @@ class PrescriptionLibraryLogic extends BaseLogic
|
|||||||
return count(array_intersect($myRoles, $allowRoles)) > 0;
|
return count(array_intersect($myRoles, $allowRoles)) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notes 是否具备消费者/诊间开方相关菜单权限(用于处方库列表鉴权别名)
|
||||||
|
*/
|
||||||
|
public static function hasPrescriptionOperatePermission(int $adminId): bool
|
||||||
|
{
|
||||||
|
$cache = new AdminAuthCache($adminId);
|
||||||
|
$uris = $cache->getAdminUri() ?? [];
|
||||||
|
$normalized = array_map(static fn ($item) => strtolower((string) $item), $uris);
|
||||||
|
$allowed = [
|
||||||
|
'tcm.prescription/lists',
|
||||||
|
'tcm.prescription/add',
|
||||||
|
'tcm.prescription/edit',
|
||||||
|
'tcm.prescription/detail',
|
||||||
|
'cf.prescription/lists',
|
||||||
|
'cf.prescription/add',
|
||||||
|
'cf.prescription/edit',
|
||||||
|
'cf.prescription/read',
|
||||||
|
'cf.prescription/del',
|
||||||
|
'cf.prescription/audit',
|
||||||
|
'wcf.prescription/lists',
|
||||||
|
'wcf.prescription/read',
|
||||||
|
'wcf.prescription/add',
|
||||||
|
'wcf.prescription/edit',
|
||||||
|
'wcf.prescription/delete',
|
||||||
|
'tcm.prescriptionlibrary/lists',
|
||||||
|
];
|
||||||
|
|
||||||
|
return count(array_intersect($allowed, $normalized)) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notes 开方页按医师 creator_id 拉取处方库:本人 / 超管角色 / 有开方菜单权限(医助代开方)
|
||||||
|
*/
|
||||||
|
public static function canListLibraryForCreator(int $adminId, array $adminInfo, int $targetCreatorId): bool
|
||||||
|
{
|
||||||
|
if ($targetCreatorId <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($targetCreatorId === $adminId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (self::canManageAllPrescriptions($adminId, $adminInfo)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::hasPrescriptionOperatePermission($adminId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @notes 添加处方库
|
* @notes 添加处方库
|
||||||
*/
|
*/
|
||||||
public static function add(array $params): ?int
|
public static function add(array $params): ?int
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$params['formula_type'] = in_array($params['formula_type'] ?? '', ['主方', '辅方'], true)
|
||||||
|
? $params['formula_type']
|
||||||
|
: '主方';
|
||||||
|
|
||||||
// 处理药材数据
|
// 处理药材数据
|
||||||
if (isset($params['herbs']) && is_array($params['herbs'])) {
|
if (isset($params['herbs']) && is_array($params['herbs'])) {
|
||||||
$params['herbs'] = json_encode($params['herbs'], JSON_UNESCAPED_UNICODE);
|
$params['herbs'] = json_encode($params['herbs'], JSON_UNESCAPED_UNICODE);
|
||||||
@@ -69,6 +122,12 @@ class PrescriptionLibraryLogic extends BaseLogic
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($params['formula_type'])) {
|
||||||
|
$params['formula_type'] = in_array($params['formula_type'], ['主方', '辅方'], true)
|
||||||
|
? $params['formula_type']
|
||||||
|
: '主方';
|
||||||
|
}
|
||||||
|
|
||||||
// 处理药材数据
|
// 处理药材数据
|
||||||
if (isset($params['herbs']) && is_array($params['herbs'])) {
|
if (isset($params['herbs']) && is_array($params['herbs'])) {
|
||||||
$params['herbs'] = json_encode($params['herbs'], JSON_UNESCAPED_UNICODE);
|
$params['herbs'] = json_encode($params['herbs'], JSON_UNESCAPED_UNICODE);
|
||||||
|
|||||||
@@ -159,6 +159,35 @@ class PrescriptionLogic
|
|||||||
return $ts ? date('Y-m-d', $ts) : date('Y-m-d');
|
return $ts ? date('Y-m-d', $ts) : date('Y-m-d');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notes 辅方用法 JSON(与主方 dosage/times/days 字段结构一致)
|
||||||
|
*/
|
||||||
|
private static function normalizeAuxUsage(array $params): ?array
|
||||||
|
{
|
||||||
|
$raw = $params['aux_usage'] ?? null;
|
||||||
|
if ($raw === null || $raw === '' || $raw === []) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is_string($raw)) {
|
||||||
|
$decoded = json_decode($raw, true);
|
||||||
|
$raw = is_array($decoded) ? $decoded : null;
|
||||||
|
}
|
||||||
|
if (!is_array($raw)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'dosage_amount' => isset($raw['dosage_amount']) && $raw['dosage_amount'] !== ''
|
||||||
|
? (float) $raw['dosage_amount']
|
||||||
|
: null,
|
||||||
|
'dosage_bag_count' => self::normalizeDosageBagCount($raw['dosage_bag_count'] ?? 1),
|
||||||
|
'need_decoction' => (int) ($raw['need_decoction'] ?? 0),
|
||||||
|
'bags_per_dose' => isset($raw['bags_per_dose']) ? (int) $raw['bags_per_dose'] : 1,
|
||||||
|
'times_per_day' => (int) ($raw['times_per_day'] ?? 3),
|
||||||
|
'usage_days' => (int) ($raw['usage_days'] ?? 7),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private static function normalizeDosageBagCount($raw): int
|
private static function normalizeDosageBagCount($raw): int
|
||||||
{
|
{
|
||||||
$count = (int) $raw;
|
$count = (int) $raw;
|
||||||
@@ -273,6 +302,7 @@ class PrescriptionLogic
|
|||||||
'dose_unit' => $params['dose_unit'] ?? '剂',
|
'dose_unit' => $params['dose_unit'] ?? '剂',
|
||||||
'usage_days' => (int)($params['usage_days'] ?? 7),
|
'usage_days' => (int)($params['usage_days'] ?? 7),
|
||||||
'times_per_day' => (int)($params['times_per_day'] ?? 2),
|
'times_per_day' => (int)($params['times_per_day'] ?? 2),
|
||||||
|
'aux_usage' => self::normalizeAuxUsage($params),
|
||||||
'usage_instruction' => $params['usage_instruction'] ?? '水煎服一日二次',
|
'usage_instruction' => $params['usage_instruction'] ?? '水煎服一日二次',
|
||||||
'usage_time' => $params['usage_time'] ?? '饭前',
|
'usage_time' => $params['usage_time'] ?? '饭前',
|
||||||
'usage_way' => $params['usage_way'] ?? '温水送服',
|
'usage_way' => $params['usage_way'] ?? '温水送服',
|
||||||
@@ -398,6 +428,9 @@ class PrescriptionLogic
|
|||||||
'dose_unit' => $params['dose_unit'] ?? $prescription->dose_unit,
|
'dose_unit' => $params['dose_unit'] ?? $prescription->dose_unit,
|
||||||
'usage_days' => (int)($params['usage_days'] ?? $prescription->usage_days),
|
'usage_days' => (int)($params['usage_days'] ?? $prescription->usage_days),
|
||||||
'times_per_day' => (int)($params['times_per_day'] ?? $prescription->times_per_day ?? 2),
|
'times_per_day' => (int)($params['times_per_day'] ?? $prescription->times_per_day ?? 2),
|
||||||
|
'aux_usage' => array_key_exists('aux_usage', $params)
|
||||||
|
? self::normalizeAuxUsage($params)
|
||||||
|
: $prescription->aux_usage,
|
||||||
'usage_instruction' => $params['usage_instruction'] ?? $prescription->usage_instruction,
|
'usage_instruction' => $params['usage_instruction'] ?? $prescription->usage_instruction,
|
||||||
'usage_time' => $params['usage_time'] ?? $prescription->usage_time,
|
'usage_time' => $params['usage_time'] ?? $prescription->usage_time,
|
||||||
'usage_way' => $params['usage_way'] ?? $prescription->usage_way,
|
'usage_way' => $params['usage_way'] ?? $prescription->usage_way,
|
||||||
|
|||||||
@@ -1413,6 +1413,7 @@ class PrescriptionOrderLogic
|
|||||||
|
|
||||||
$payload['official_urls'] = ExpressTrackService::officialUrls($num);
|
$payload['official_urls'] = ExpressTrackService::officialUrls($num);
|
||||||
$payload['tracking_number'] = $num;
|
$payload['tracking_number'] = $num;
|
||||||
|
$payload['order_id'] = $id;
|
||||||
$payload['express_company_used'] = $ec;
|
$payload['express_company_used'] = $ec;
|
||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
@@ -2556,7 +2557,7 @@ class PrescriptionOrderLogic
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出用:诊单医助在 admin_dept 中的部门路径(多部门「;」、路径内「 / 」)
|
* 导出用:管理员在 admin_dept 中的部门路径(多部门「;」、路径内「 / 」;业务订单导出取 creator_id)
|
||||||
*/
|
*/
|
||||||
public static function formatAssistantDeptPathForExport(int $assistantAdminId): string
|
public static function formatAssistantDeptPathForExport(int $assistantAdminId): string
|
||||||
{
|
{
|
||||||
@@ -2846,21 +2847,21 @@ class PrescriptionOrderLogic
|
|||||||
$item['export_patient_gender'] = $g === 1 ? '男' : '女';
|
$item['export_patient_gender'] = $g === 1 ? '男' : '女';
|
||||||
$item['export_patient_age'] = (string) ($dg['age'] ?? '');
|
$item['export_patient_age'] = (string) ($dg['age'] ?? '');
|
||||||
$item['export_patient_phone'] = (string) ($dg['phone'] ?? '');
|
$item['export_patient_phone'] = (string) ($dg['phone'] ?? '');
|
||||||
$rxAst = \is_array($rx) ? (int) ($rx['assistant_id'] ?? 0) : 0;
|
|
||||||
$diagAst = (int) ($dg['assistant_id'] ?? 0);
|
|
||||||
$astId = self::resolveAssistantAdminIdForPrescriptionOrderRow($rxAst, $diagAst);
|
|
||||||
if ($astId > 0) {
|
|
||||||
if (!isset($assistantDeptCache[$astId])) {
|
|
||||||
$assistantDeptCache[$astId] = self::formatAssistantDeptPathForExport($astId);
|
|
||||||
}
|
|
||||||
$item['export_assistant_dept'] = $assistantDeptCache[$astId];
|
|
||||||
} else {
|
|
||||||
$item['export_assistant_dept'] = '';
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$item['export_patient_gender'] = '';
|
$item['export_patient_gender'] = '';
|
||||||
$item['export_patient_age'] = '';
|
$item['export_patient_age'] = '';
|
||||||
$item['export_patient_phone'] = '';
|
$item['export_patient_phone'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出「医助名字 / 医助部门」:与详情 order_creator 一致,按业务订单 creator_id(非诊单二诊 assistant_id)
|
||||||
|
$creatorId = (int) ($item['creator_id'] ?? 0);
|
||||||
|
$item['assistant_name'] = (string) ($item['creator_name'] ?? '');
|
||||||
|
if ($creatorId > 0) {
|
||||||
|
if (!isset($assistantDeptCache[$creatorId])) {
|
||||||
|
$assistantDeptCache[$creatorId] = self::formatAssistantDeptPathForExport($creatorId);
|
||||||
|
}
|
||||||
|
$item['export_assistant_dept'] = $assistantDeptCache[$creatorId];
|
||||||
|
} else {
|
||||||
$item['export_assistant_dept'] = '';
|
$item['export_assistant_dept'] = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class PrescriptionLibraryValidate extends BaseValidate
|
|||||||
protected $rule = [
|
protected $rule = [
|
||||||
'id' => 'require|number',
|
'id' => 'require|number',
|
||||||
'prescription_name' => 'require|max:100',
|
'prescription_name' => 'require|max:100',
|
||||||
|
'formula_type' => 'in:主方,辅方',
|
||||||
'herbs' => 'require|array',
|
'herbs' => 'require|array',
|
||||||
'is_public' => 'in:0,1'
|
'is_public' => 'in:0,1'
|
||||||
];
|
];
|
||||||
@@ -23,6 +24,7 @@ class PrescriptionLibraryValidate extends BaseValidate
|
|||||||
'id.number' => '处方ID必须为数字',
|
'id.number' => '处方ID必须为数字',
|
||||||
'prescription_name.require' => '处方名称不能为空',
|
'prescription_name.require' => '处方名称不能为空',
|
||||||
'prescription_name.max' => '处方名称最多100个字符',
|
'prescription_name.max' => '处方名称最多100个字符',
|
||||||
|
'formula_type.in' => '处方类型只能是主方或辅方',
|
||||||
'herbs.require' => '药材列表不能为空',
|
'herbs.require' => '药材列表不能为空',
|
||||||
'herbs.array' => '药材列表格式错误',
|
'herbs.array' => '药材列表格式错误',
|
||||||
'is_public.in' => '是否公开参数错误'
|
'is_public.in' => '是否公开参数错误'
|
||||||
@@ -33,7 +35,7 @@ class PrescriptionLibraryValidate extends BaseValidate
|
|||||||
*/
|
*/
|
||||||
public function sceneAdd()
|
public function sceneAdd()
|
||||||
{
|
{
|
||||||
return $this->only(['prescription_name', 'herbs', 'is_public']);
|
return $this->only(['prescription_name', 'formula_type', 'herbs', 'is_public']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,7 +43,7 @@ class PrescriptionLibraryValidate extends BaseValidate
|
|||||||
*/
|
*/
|
||||||
public function sceneEdit()
|
public function sceneEdit()
|
||||||
{
|
{
|
||||||
return $this->only(['id', 'prescription_name', 'herbs', 'is_public']);
|
return $this->only(['id', 'prescription_name', 'formula_type', 'herbs', 'is_public']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class PrescriptionValidate extends BaseValidate
|
|||||||
'patient_name', 'gender', 'age',
|
'patient_name', 'gender', 'age',
|
||||||
'visit_no', 'prescription_date', 'tongue', 'tongue_image', 'pulse',
|
'visit_no', 'prescription_date', 'tongue', 'tongue_image', 'pulse',
|
||||||
'pulse_condition', 'clinical_diagnosis', 'herbs', 'dose_count', 'dose_unit',
|
'pulse_condition', 'clinical_diagnosis', 'herbs', 'dose_count', 'dose_unit',
|
||||||
'usage_days', 'times_per_day', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
|
'usage_days', 'times_per_day', 'aux_usage', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
|
||||||
'usage_notes', 'doctor_name', 'is_shared', 'visible_role_ids',
|
'usage_notes', 'doctor_name', 'is_shared', 'visible_role_ids',
|
||||||
'diagnosis_id', 'appointment_id', 'audit_status',
|
'diagnosis_id', 'appointment_id', 'audit_status',
|
||||||
]);
|
]);
|
||||||
@@ -54,7 +54,7 @@ class PrescriptionValidate extends BaseValidate
|
|||||||
'patient_name', 'gender', 'age',
|
'patient_name', 'gender', 'age',
|
||||||
'visit_no', 'prescription_date', 'tongue', 'tongue_image', 'pulse',
|
'visit_no', 'prescription_date', 'tongue', 'tongue_image', 'pulse',
|
||||||
'pulse_condition', 'clinical_diagnosis', 'herbs', 'dose_count', 'dose_unit',
|
'pulse_condition', 'clinical_diagnosis', 'herbs', 'dose_count', 'dose_unit',
|
||||||
'usage_days', 'times_per_day', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
|
'usage_days', 'times_per_day', 'aux_usage', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
|
||||||
'usage_notes', 'doctor_name', 'is_shared', 'visible_role_ids', 'diagnosis_id',
|
'usage_notes', 'doctor_name', 'is_shared', 'visible_role_ids', 'diagnosis_id',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use think\console\Output;
|
|||||||
* php think gancao:sync-logistics 默认拉 100 单
|
* php think gancao:sync-logistics 默认拉 100 单
|
||||||
* php think gancao:sync-logistics --limit=200 自定义拉取上限
|
* php think gancao:sync-logistics --limit=200 自定义拉取上限
|
||||||
* php think gancao:sync-logistics --order-id=123 只跑指定订单
|
* php think gancao:sync-logistics --order-id=123 只跑指定订单
|
||||||
|
* php think gancao:sync-logistics -t SF1234567890 按快递单号走快递100 查询并落库
|
||||||
* php think gancao:sync-logistics --detail 打印每单详细
|
* php think gancao:sync-logistics --detail 打印每单详细
|
||||||
*
|
*
|
||||||
* 建议 crontab(每 30 分钟执行一次):
|
* 建议 crontab(每 30 分钟执行一次):
|
||||||
@@ -40,6 +41,7 @@ class GancaoSyncLogisticsRoute extends Command
|
|||||||
->setDescription('同步甘草订单的物流路由(GET_TASK_ROUTE_LIST)到本地物流追踪表')
|
->setDescription('同步甘草订单的物流路由(GET_TASK_ROUTE_LIST)到本地物流追踪表')
|
||||||
->addOption('limit', 'l', Option::VALUE_OPTIONAL, '本次最多处理多少条订单', 100)
|
->addOption('limit', 'l', Option::VALUE_OPTIONAL, '本次最多处理多少条订单', 100)
|
||||||
->addOption('order-id', null, Option::VALUE_OPTIONAL, '只同步指定 prescription_order.id', null)
|
->addOption('order-id', null, Option::VALUE_OPTIONAL, '只同步指定 prescription_order.id', null)
|
||||||
|
->addOption('tracking-number', 't', Option::VALUE_REQUIRED, '按快递单号走快递100 查询并落库')
|
||||||
->addOption('detail', 'd', Option::VALUE_NONE, '打印每单详细结果');
|
->addOption('detail', 'd', Option::VALUE_NONE, '打印每单详细结果');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,26 +50,43 @@ class GancaoSyncLogisticsRoute extends Command
|
|||||||
$limit = max(1, (int) $input->getOption('limit'));
|
$limit = max(1, (int) $input->getOption('limit'));
|
||||||
$onlyOrderId = $input->getOption('order-id');
|
$onlyOrderId = $input->getOption('order-id');
|
||||||
$onlyOrderId = $onlyOrderId !== null ? (int) $onlyOrderId : null;
|
$onlyOrderId = $onlyOrderId !== null ? (int) $onlyOrderId : null;
|
||||||
|
$trackingNumber = trim((string) $input->getOption('tracking-number'));
|
||||||
$verbose = (bool) $input->getOption('detail');
|
$verbose = (bool) $input->getOption('detail');
|
||||||
|
|
||||||
|
if ($trackingNumber !== '' && $onlyOrderId !== null) {
|
||||||
|
$output->error('请勿同时使用 --tracking-number 与 --order-id');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
$output->writeln('========================================');
|
$output->writeln('========================================');
|
||||||
$output->writeln('甘草物流路由同步');
|
$output->writeln($trackingNumber !== '' ? '快递100 物流查询' : '甘草物流路由同步');
|
||||||
$output->writeln('========================================');
|
$output->writeln('========================================');
|
||||||
|
|
||||||
if (!GancaoScmRecipelService::isConfigured()) {
|
if ($trackingNumber === '' && !GancaoScmRecipelService::isConfigured()) {
|
||||||
$output->error('甘草 SCM 未配置:' . GancaoScmRecipelService::whyNotConfigured());
|
$output->error('甘草 SCM 未配置:' . GancaoScmRecipelService::whyNotConfigured());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$start = microtime(true);
|
$start = microtime(true);
|
||||||
|
if ($trackingNumber !== '') {
|
||||||
|
$output->writeln('开始查询...(快递100,tracking_number=' . $trackingNumber . ')');
|
||||||
|
} else {
|
||||||
$output->writeln('开始同步...(limit=' . $limit . ($onlyOrderId ? ', order_id=' . $onlyOrderId : '') . ')');
|
$output->writeln('开始同步...(limit=' . $limit . ($onlyOrderId ? ', order_id=' . $onlyOrderId : '') . ')');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if ($trackingNumber !== '') {
|
||||||
|
$stats = ExpressTrackingService::queryKuaidiByTrackingNumber($trackingNumber);
|
||||||
|
$stats['reconcile_cleared'] = 0;
|
||||||
|
$stats['reconcile_scanned'] = 0;
|
||||||
|
$stats['reconcile_lines'] = [];
|
||||||
|
} else {
|
||||||
$stats = GancaoLogisticsRouteService::syncBatch($limit, $onlyOrderId);
|
$stats = GancaoLogisticsRouteService::syncBatch($limit, $onlyOrderId);
|
||||||
$recon = ExpressTrackingService::reconcileAssistantReleaseForShippedPrescriptionOrders(200);
|
$recon = ExpressTrackingService::reconcileAssistantReleaseForShippedPrescriptionOrders(2000);
|
||||||
$stats['reconcile_cleared'] = (int) ($recon['cleared'] ?? 0);
|
$stats['reconcile_cleared'] = (int) ($recon['cleared'] ?? 0);
|
||||||
$stats['reconcile_scanned'] = (int) ($recon['scanned'] ?? 0);
|
$stats['reconcile_scanned'] = (int) ($recon['scanned'] ?? 0);
|
||||||
$stats['reconcile_lines'] = $recon['lines'] ?? [];
|
$stats['reconcile_lines'] = $recon['lines'] ?? [];
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$output->error('同步异常:' . $e->getMessage());
|
$output->error('同步异常:' . $e->getMessage());
|
||||||
return 1;
|
return 1;
|
||||||
@@ -81,7 +100,7 @@ class GancaoSyncLogisticsRoute extends Command
|
|||||||
foreach ($stats['details'] as $row) {
|
foreach ($stats['details'] as $row) {
|
||||||
$tag = !empty($row['success']) ? '[OK]' : '[FAIL]';
|
$tag = !empty($row['success']) ? '[OK]' : '[FAIL]';
|
||||||
$line = sprintf(
|
$line = sprintf(
|
||||||
'%s order_id=%s order_no=%s app_order_no=%s tn=%s state=%s traces=+%s msg=%s',
|
'%s order_id=%s order_no=%s app_order_no=%s tn=%s state=%s traces=+%s source=%s msg=%s',
|
||||||
$tag,
|
$tag,
|
||||||
$row['order_id'] ?? '',
|
$row['order_id'] ?? '',
|
||||||
$row['order_no'] ?? '',
|
$row['order_no'] ?? '',
|
||||||
@@ -89,6 +108,7 @@ class GancaoSyncLogisticsRoute extends Command
|
|||||||
$row['tracking_number'] ?? '',
|
$row['tracking_number'] ?? '',
|
||||||
$row['state'] ?? '',
|
$row['state'] ?? '',
|
||||||
$row['traces'] ?? 0,
|
$row['traces'] ?? 0,
|
||||||
|
$row['source'] ?? '',
|
||||||
$row['message'] ?? ''
|
$row['message'] ?? ''
|
||||||
);
|
);
|
||||||
$output->writeln($line);
|
$output->writeln($line);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class Prescription extends BaseModel
|
|||||||
protected $deleteTime = 'delete_time';
|
protected $deleteTime = 'delete_time';
|
||||||
protected $dateFormat = false;
|
protected $dateFormat = false;
|
||||||
|
|
||||||
protected $json = ['herbs', 'case_record'];
|
protected $json = ['herbs', 'case_record', 'aux_usage'];
|
||||||
protected $jsonAssoc = true;
|
protected $jsonAssoc = true;
|
||||||
|
|
||||||
// 字段类型转换
|
// 字段类型转换
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class ExpressTrackService
|
|||||||
$carrier = $resolved['carrier'];
|
$carrier = $resolved['carrier'];
|
||||||
$kuaidiCom = $resolved['kuaidi_com'];
|
$kuaidiCom = $resolved['kuaidi_com'];
|
||||||
$label = $resolved['label'];
|
$label = $resolved['label'];
|
||||||
|
$comCandidates = self::kuaidiComCandidates($kuaidiCom, $num);
|
||||||
|
|
||||||
$officialUrl = self::buildOfficialUrl($carrier, $num);
|
$officialUrl = self::buildOfficialUrl($carrier, $num);
|
||||||
|
|
||||||
@@ -75,6 +76,74 @@ class ExpressTrackService
|
|||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$lastFail = null;
|
||||||
|
foreach ($comCandidates as $tryCom) {
|
||||||
|
$tryOut = self::queryKuaidiOnce($cfg, $tryCom, $num, $phoneForKuaidi, $carrier, $label);
|
||||||
|
if (!empty($tryOut['traces']) || ($tryOut['state'] ?? '') !== '') {
|
||||||
|
return $tryOut;
|
||||||
|
}
|
||||||
|
$lastFail = $tryOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lastFail ?? $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据运单号形态纠正承运商(避免 express_tracking 误存 sf 导致京东单查不出)
|
||||||
|
*/
|
||||||
|
public static function normalizeExpressCompanyCode(string $trackingNumber, string $storedCompany = 'auto'): string
|
||||||
|
{
|
||||||
|
$byNumber = self::detectCarrierFromNumber($trackingNumber);
|
||||||
|
if ($byNumber === null) {
|
||||||
|
$ec = strtolower(trim($storedCompany));
|
||||||
|
|
||||||
|
return in_array($ec, ['sf', 'jd', 'jt', 'jtexpress', 'auto'], true) ? $ec : 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
$ec = strtolower(trim($storedCompany));
|
||||||
|
$byEc = self::carrierFromExpressCode($ec);
|
||||||
|
if ($byEc !== null && $byEc['carrier'] !== $byNumber['carrier']) {
|
||||||
|
return $byNumber['carrier'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $byNumber['carrier'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $cfg
|
||||||
|
* @return array{
|
||||||
|
* carrier: string,
|
||||||
|
* carrier_label: string,
|
||||||
|
* kuaidi_com: string,
|
||||||
|
* traces: list<array{time:string,context:string}>,
|
||||||
|
* state: string,
|
||||||
|
* state_text: string,
|
||||||
|
* source: string,
|
||||||
|
* hint: string,
|
||||||
|
* official_url: string
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
private static function queryKuaidiOnce(
|
||||||
|
array $cfg,
|
||||||
|
string $kuaidiCom,
|
||||||
|
string $num,
|
||||||
|
string $phoneForKuaidi,
|
||||||
|
string $carrier,
|
||||||
|
string $label
|
||||||
|
): array {
|
||||||
|
$officialUrl = self::buildOfficialUrl($carrier, $num);
|
||||||
|
$out = [
|
||||||
|
'carrier' => $carrier,
|
||||||
|
'carrier_label' => $label,
|
||||||
|
'kuaidi_com' => $kuaidiCom,
|
||||||
|
'traces' => [],
|
||||||
|
'state' => '',
|
||||||
|
'state_text' => '',
|
||||||
|
'source' => 'kuaidi100',
|
||||||
|
'hint' => '',
|
||||||
|
'official_url' => $officialUrl,
|
||||||
|
];
|
||||||
|
|
||||||
$paramArr = [
|
$paramArr = [
|
||||||
'com' => $kuaidiCom,
|
'com' => $kuaidiCom,
|
||||||
'num' => $num,
|
'num' => $num,
|
||||||
@@ -98,7 +167,7 @@ class ExpressTrackService
|
|||||||
$raw = self::httpPostForm($url, $postBody);
|
$raw = self::httpPostForm($url, $postBody);
|
||||||
if ($raw === null || $raw === '') {
|
if ($raw === null || $raw === '') {
|
||||||
$out['hint'] = '快递100接口无响应,请稍后重试或使用官网查询';
|
$out['hint'] = '快递100接口无响应,请稍后重试或使用官网查询';
|
||||||
Log::warning('ExpressTrackService kuaidi100 empty response', ['num' => $num]);
|
Log::warning('ExpressTrackService kuaidi100 empty response', ['num' => $num, 'com' => $kuaidiCom]);
|
||||||
|
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
@@ -106,7 +175,7 @@ class ExpressTrackService
|
|||||||
$json = json_decode($raw, true);
|
$json = json_decode($raw, true);
|
||||||
if (!is_array($json)) {
|
if (!is_array($json)) {
|
||||||
$out['hint'] = '快递100返回异常,请使用官网查询';
|
$out['hint'] = '快递100返回异常,请使用官网查询';
|
||||||
Log::warning('ExpressTrackService kuaidi100 invalid json', ['raw' => mb_substr($raw, 0, 500)]);
|
Log::warning('ExpressTrackService kuaidi100 invalid json', ['raw' => mb_substr($raw, 0, 500), 'com' => $kuaidiCom]);
|
||||||
|
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
@@ -114,19 +183,16 @@ class ExpressTrackService
|
|||||||
if (isset($json['result']) && $json['result'] === false) {
|
if (isset($json['result']) && $json['result'] === false) {
|
||||||
$msg = (string) ($json['message'] ?? '查询失败');
|
$msg = (string) ($json['message'] ?? '查询失败');
|
||||||
$returnCode = (string) ($json['returnCode'] ?? '');
|
$returnCode = (string) ($json['returnCode'] ?? '');
|
||||||
|
|
||||||
// 特殊处理"找不到对应公司"错误
|
|
||||||
if ($msg === '找不到对应公司' || $returnCode === '400') {
|
if ($msg === '找不到对应公司' || $returnCode === '400') {
|
||||||
$out['hint'] = '快递100暂不支持该快递公司或编码错误,请使用下方官网链接查询';
|
$out['hint'] = '快递100暂不支持该快递公司或编码错误,请使用下方官网链接查询';
|
||||||
} else {
|
} else {
|
||||||
$out['hint'] = $msg;
|
$out['hint'] = $msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::info('ExpressTrackService kuaidi100 business fail', [
|
Log::info('ExpressTrackService kuaidi100 business fail', [
|
||||||
'message' => $msg,
|
'message' => $msg,
|
||||||
'returnCode' => $returnCode,
|
'returnCode' => $returnCode,
|
||||||
'num' => $num,
|
'num' => $num,
|
||||||
'com' => $kuaidiCom
|
'com' => $kuaidiCom,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $out;
|
return $out;
|
||||||
@@ -138,7 +204,7 @@ class ExpressTrackService
|
|||||||
}
|
}
|
||||||
if (($json['message'] ?? '') !== 'ok' && $data === []) {
|
if (($json['message'] ?? '') !== 'ok' && $data === []) {
|
||||||
$out['hint'] = (string) ($json['message'] ?? '未查到轨迹');
|
$out['hint'] = (string) ($json['message'] ?? '未查到轨迹');
|
||||||
Log::info('ExpressTrackService kuaidi100 no data', ['json' => $json]);
|
Log::info('ExpressTrackService kuaidi100 no data', ['json' => $json, 'com' => $kuaidiCom]);
|
||||||
|
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
@@ -159,12 +225,35 @@ class ExpressTrackService
|
|||||||
$out['traces'] = $traces;
|
$out['traces'] = $traces;
|
||||||
$out['state'] = (string) ($json['state'] ?? '');
|
$out['state'] = (string) ($json['state'] ?? '');
|
||||||
$out['state_text'] = self::stateText($out['state']);
|
$out['state_text'] = self::stateText($out['state']);
|
||||||
$out['source'] = 'kuaidi100';
|
|
||||||
$out['hint'] = $traces === [] ? '暂无轨迹节点,单号可能尚未揽收' : '';
|
$out['hint'] = $traces === [] ? '暂无轨迹节点,单号可能尚未揽收' : '';
|
||||||
|
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
private static function kuaidiComCandidates(string $primaryCom, string $num): array
|
||||||
|
{
|
||||||
|
$list = [$primaryCom];
|
||||||
|
$byNumber = self::detectCarrierFromNumber($num);
|
||||||
|
if ($byNumber !== null && !in_array($byNumber['kuaidi_com'], $list, true)) {
|
||||||
|
$list[] = $byNumber['kuaidi_com'];
|
||||||
|
}
|
||||||
|
if (preg_match('/^JDVE/i', strtoupper($num)) && !in_array('jd', $list, true)) {
|
||||||
|
$list[] = 'jd';
|
||||||
|
}
|
||||||
|
if (preg_match('/^(JD|JDV|JDK|JDEX)/i', strtoupper($num))) {
|
||||||
|
foreach (['jingdong', 'jd'] as $c) {
|
||||||
|
if (!in_array($c, $list, true)) {
|
||||||
|
$list[] = $c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique(array_filter($list, static fn ($c) => $c !== '' && $c !== 'auto')));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 快递100「phone」入参:有手动覆盖且不少于 4 位时用覆盖;否则用订单收货号码。
|
* 快递100「phone」入参:有手动覆盖且不少于 4 位时用覆盖;否则用订单收货号码。
|
||||||
* 对 11 位及以上数字取后 11 位作为手机号(去掉可能的前缀符号位)。
|
* 对 11 位及以上数字取后 11 位作为手机号(去掉可能的前缀符号位)。
|
||||||
@@ -191,32 +280,75 @@ class ExpressTrackService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{carrier: string, kuaidi_com: string, label: string}
|
* @return array{carrier: string, kuaidi_com: string, label: string}|null
|
||||||
*/
|
*/
|
||||||
private static function resolveCarrier(string $expressCompany, string $num): array
|
private static function carrierFromExpressCode(string $expressCompany): ?array
|
||||||
{
|
{
|
||||||
$ec = strtolower(trim($expressCompany));
|
$ec = strtolower(trim($expressCompany));
|
||||||
if ($ec === 'sf') {
|
if ($ec === 'sf' || $ec === 'shunfeng') {
|
||||||
return ['carrier' => 'sf', 'kuaidi_com' => self::KUAIDI_COM_SF, 'label' => '顺丰速运'];
|
return ['carrier' => 'sf', 'kuaidi_com' => self::KUAIDI_COM_SF, 'label' => '顺丰速运'];
|
||||||
}
|
}
|
||||||
if ($ec === 'jd') {
|
if ($ec === 'jd' || $ec === 'jingdong') {
|
||||||
return ['carrier' => 'jd', 'kuaidi_com' => self::KUAIDI_COM_JD, 'label' => '京东快递'];
|
return ['carrier' => 'jd', 'kuaidi_com' => self::KUAIDI_COM_JD, 'label' => '京东快递'];
|
||||||
}
|
}
|
||||||
if ($ec === 'jt' || $ec === 'jtexpress') {
|
if ($ec === 'jt' || $ec === 'jtexpress') {
|
||||||
return ['carrier' => 'jt', 'kuaidi_com' => self::KUAIDI_COM_JT, 'label' => '极兔速递'];
|
return ['carrier' => 'jt', 'kuaidi_com' => self::KUAIDI_COM_JT, 'label' => '极兔速递'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$u = strtoupper($num);
|
return null;
|
||||||
if (preg_match('/^SF\d/i', $num)) {
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{carrier: string, kuaidi_com: string, label: string}|null
|
||||||
|
*/
|
||||||
|
private static function detectCarrierFromNumber(string $num): ?array
|
||||||
|
{
|
||||||
|
$n = trim($num);
|
||||||
|
if ($n === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$u = strtoupper($n);
|
||||||
|
if (preg_match('/^SF\d/i', $n)) {
|
||||||
return ['carrier' => 'sf', 'kuaidi_com' => self::KUAIDI_COM_SF, 'label' => '顺丰速运(单号识别)'];
|
return ['carrier' => 'sf', 'kuaidi_com' => self::KUAIDI_COM_SF, 'label' => '顺丰速运(单号识别)'];
|
||||||
}
|
}
|
||||||
if (preg_match('/^(JD|JDV|JDK|JDEX|JD[A-Z0-9])/i', $u)) {
|
if (preg_match('/^JDVE/i', $u)) {
|
||||||
return ['carrier' => 'jd', 'kuaidi_com' => self::KUAIDI_COM_JD, 'label' => '京东快递(单号识别)'];
|
return ['carrier' => 'jd', 'kuaidi_com' => self::KUAIDI_COM_JD, 'label' => '京东快递(单号识别)'];
|
||||||
}
|
}
|
||||||
if (preg_match('/^JT\d{13}$/i', $num)) {
|
if (preg_match('/^(JDK|JDV|JDEX)/i', $u) || preg_match('/^JD[A-Z0-9]{10,}/i', $u)) {
|
||||||
|
return ['carrier' => 'jd', 'kuaidi_com' => self::KUAIDI_COM_JD, 'label' => '京东物流(单号识别)'];
|
||||||
|
}
|
||||||
|
if (preg_match('/^JT\d{13}$/i', $n)) {
|
||||||
return ['carrier' => 'jt', 'kuaidi_com' => self::KUAIDI_COM_JT, 'label' => '极兔速递(单号识别)'];
|
return ['carrier' => 'jt', 'kuaidi_com' => self::KUAIDI_COM_JT, 'label' => '极兔速递(单号识别)'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{carrier: string, kuaidi_com: string, label: string}
|
||||||
|
*/
|
||||||
|
private static function resolveCarrier(string $expressCompany, string $num): array
|
||||||
|
{
|
||||||
|
$byNumber = self::detectCarrierFromNumber($num);
|
||||||
|
$byEc = self::carrierFromExpressCode($expressCompany);
|
||||||
|
|
||||||
|
if ($byNumber !== null && $byEc !== null && $byEc['carrier'] !== $byNumber['carrier']) {
|
||||||
|
Log::info('ExpressTrackService carrier mismatch, prefer tracking number', [
|
||||||
|
'express_company' => $expressCompany,
|
||||||
|
'tracking_number' => $num,
|
||||||
|
'stored_carrier' => $byEc['carrier'],
|
||||||
|
'detected_carrier' => $byNumber['carrier'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $byNumber;
|
||||||
|
}
|
||||||
|
if ($byEc !== null) {
|
||||||
|
return $byEc;
|
||||||
|
}
|
||||||
|
if ($byNumber !== null) {
|
||||||
|
return $byNumber;
|
||||||
|
}
|
||||||
|
|
||||||
return ['carrier' => 'auto', 'kuaidi_com' => 'auto', 'label' => '自动识别'];
|
return ['carrier' => 'auto', 'kuaidi_com' => 'auto', 'label' => '自动识别'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,10 @@ class ExpressTrackingService
|
|||||||
|
|
||||||
$tracking->order_id = (int) ($params['order_id'] ?? 0);
|
$tracking->order_id = (int) ($params['order_id'] ?? 0);
|
||||||
$tracking->order_type = (string) ($params['order_type'] ?? 'prescription');
|
$tracking->order_type = (string) ($params['order_type'] ?? 'prescription');
|
||||||
$tracking->express_company = (string) ($params['express_company'] ?? 'auto');
|
$tracking->express_company = ExpressTrackService::normalizeExpressCompanyCode(
|
||||||
|
$trackingNumber,
|
||||||
|
(string) ($params['express_company'] ?? 'auto')
|
||||||
|
);
|
||||||
$tracking->recipient_phone = (string) ($params['recipient_phone'] ?? '');
|
$tracking->recipient_phone = (string) ($params['recipient_phone'] ?? '');
|
||||||
$tracking->recipient_name = (string) ($params['recipient_name'] ?? '');
|
$tracking->recipient_name = (string) ($params['recipient_name'] ?? '');
|
||||||
$tracking->recipient_address = (string) ($params['recipient_address'] ?? '');
|
$tracking->recipient_address = (string) ($params['recipient_address'] ?? '');
|
||||||
@@ -78,6 +81,177 @@ class ExpressTrackingService
|
|||||||
return $tracking;
|
return $tracking;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按快递单号走快递100 查询并落库(CLI:gancao:sync-logistics -t)
|
||||||
|
*
|
||||||
|
* @return array{total:int, success:int, failed:int, skipped:int, assistant_cleared:int, assistant_lines:list<string>, details:array<int, array<string,mixed>>}
|
||||||
|
*/
|
||||||
|
public static function queryKuaidiByTrackingNumber(string $trackingNumber): array
|
||||||
|
{
|
||||||
|
$stats = [
|
||||||
|
'total' => 0,
|
||||||
|
'success' => 0,
|
||||||
|
'failed' => 0,
|
||||||
|
'skipped' => 0,
|
||||||
|
'assistant_cleared' => 0,
|
||||||
|
'assistant_skipped_assign_log' => 0,
|
||||||
|
'assistant_lines' => [],
|
||||||
|
'details' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
$tn = trim($trackingNumber);
|
||||||
|
if ($tn === '') {
|
||||||
|
$stats['failed'] = 1;
|
||||||
|
$stats['details'][] = [
|
||||||
|
'success' => false,
|
||||||
|
'tracking_number' => '',
|
||||||
|
'message' => '快递单号不能为空',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tracking = self::resolveTrackingByNumber($tn);
|
||||||
|
if (!$tracking) {
|
||||||
|
$stats['failed'] = 1;
|
||||||
|
$stats['details'][] = [
|
||||||
|
'success' => false,
|
||||||
|
'tracking_number' => $tn,
|
||||||
|
'message' => '未找到该快递单号对应的业务订单或物流追踪记录',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stats['total'] = 1;
|
||||||
|
self::backfillRecipientPhoneFromPrescriptionOrder($tracking);
|
||||||
|
self::syncTrackingExpressCompanyFromNumber($tracking);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = self::queryAndUpdate((int) $tracking->id, false);
|
||||||
|
$ok = self::isKuaidiQuerySuccessful($result);
|
||||||
|
$detail = [
|
||||||
|
'order_id' => (int) $tracking->order_id,
|
||||||
|
'tracking_number' => (string) $tracking->tracking_number,
|
||||||
|
'success' => $ok,
|
||||||
|
'message' => trim((string) ($result['hint'] ?? '')) ?: 'ok',
|
||||||
|
'traces' => count($result['traces'] ?? []),
|
||||||
|
'state' => (string) ($result['state_text'] ?? $tracking->current_state_text ?? ''),
|
||||||
|
'source' => (string) ($result['source'] ?? ''),
|
||||||
|
];
|
||||||
|
if ($ok) {
|
||||||
|
$stats['success'] = 1;
|
||||||
|
} else {
|
||||||
|
$stats['failed'] = 1;
|
||||||
|
}
|
||||||
|
$stats['details'][] = $detail;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$stats['failed'] = 1;
|
||||||
|
$stats['details'][] = [
|
||||||
|
'order_id' => (int) $tracking->order_id,
|
||||||
|
'tracking_number' => (string) $tracking->tracking_number,
|
||||||
|
'success' => false,
|
||||||
|
'message' => '异常:' . $e->getMessage(),
|
||||||
|
];
|
||||||
|
Log::error('queryKuaidiByTrackingNumber failed', [
|
||||||
|
'tracking_number' => $tn,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保 express_tracking 存在(不发起查询)
|
||||||
|
*/
|
||||||
|
private static function resolveTrackingByNumber(string $trackingNumber): ?ExpressTracking
|
||||||
|
{
|
||||||
|
$tn = trim($trackingNumber);
|
||||||
|
if ($tn === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tracking = ExpressTracking::whereRaw('TRIM(`tracking_number`) = ?', [$tn])
|
||||||
|
->whereNull('delete_time')
|
||||||
|
->find();
|
||||||
|
|
||||||
|
$order = PrescriptionOrder::whereNull('delete_time')
|
||||||
|
->whereRaw('TRIM(`tracking_number`) = ?', [$tn])
|
||||||
|
->order('id', 'desc')
|
||||||
|
->find();
|
||||||
|
|
||||||
|
if ($tracking && $order) {
|
||||||
|
$dirty = false;
|
||||||
|
if ((int) $tracking->order_id !== (int) $order->id) {
|
||||||
|
$tracking->order_id = (int) $order->id;
|
||||||
|
$dirty = true;
|
||||||
|
}
|
||||||
|
if (trim((string) ($tracking->recipient_phone ?? '')) === '') {
|
||||||
|
$tracking->recipient_phone = (string) ($order->recipient_phone ?? '');
|
||||||
|
$dirty = true;
|
||||||
|
}
|
||||||
|
if ($dirty) {
|
||||||
|
$tracking->update_time = time();
|
||||||
|
$tracking->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tracking;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tracking) {
|
||||||
|
self::backfillRecipientPhoneFromPrescriptionOrder($tracking);
|
||||||
|
|
||||||
|
return $tracking;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = time();
|
||||||
|
$tracking = new ExpressTracking();
|
||||||
|
$tracking->tracking_number = mb_substr($tn, 0, 80);
|
||||||
|
$tracking->create_time = $now;
|
||||||
|
$tracking->order_id = (int) $order->id;
|
||||||
|
$tracking->order_type = 'prescription';
|
||||||
|
$tracking->express_company = ExpressTrackService::normalizeExpressCompanyCode(
|
||||||
|
$tn,
|
||||||
|
(string) ($order->express_company ?? 'auto')
|
||||||
|
);
|
||||||
|
$tracking->recipient_phone = (string) ($order->recipient_phone ?? '');
|
||||||
|
$tracking->recipient_name = (string) ($order->recipient_name ?? '');
|
||||||
|
$tracking->recipient_address = (string) ($order->shipping_address ?? '');
|
||||||
|
$tracking->next_update_time = $now;
|
||||||
|
$tracking->update_time = $now;
|
||||||
|
$tracking->save();
|
||||||
|
|
||||||
|
return $tracking;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed>|null $result
|
||||||
|
*/
|
||||||
|
private static function isKuaidiQuerySuccessful(?array $result): bool
|
||||||
|
{
|
||||||
|
if (!is_array($result)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$source = (string) ($result['source'] ?? '');
|
||||||
|
if ($source !== 'kuaidi100') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$hint = trim((string) ($result['hint'] ?? ''));
|
||||||
|
if ($hint === '') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (str_contains($hint, '暂无轨迹')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询并更新物流信息
|
* 查询并更新物流信息
|
||||||
*
|
*
|
||||||
@@ -96,13 +270,14 @@ class ExpressTrackingService
|
|||||||
}
|
}
|
||||||
|
|
||||||
self::backfillRecipientPhoneFromPrescriptionOrder($tracking);
|
self::backfillRecipientPhoneFromPrescriptionOrder($tracking);
|
||||||
|
self::syncTrackingExpressCompanyFromNumber($tracking);
|
||||||
|
|
||||||
$startTime = microtime(true);
|
$startTime = microtime(true);
|
||||||
|
|
||||||
// 调用快递100查询(顺丰等单号需带收件人手机号后四位,见 ExpressTrackService)
|
// 调用快递100查询(顺丰等单号需带收件人手机号后四位,见 ExpressTrackService)
|
||||||
$result = ExpressTrackService::query(
|
$result = ExpressTrackService::query(
|
||||||
$tracking->express_company,
|
(string) $tracking->express_company,
|
||||||
$tracking->tracking_number,
|
(string) $tracking->tracking_number,
|
||||||
(string) $tracking->recipient_phone
|
(string) $tracking->recipient_phone
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -547,6 +722,27 @@ class ExpressTrackingService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运单号与 express_company 不一致时按单号纠正(如京东单误存 sf)
|
||||||
|
*/
|
||||||
|
private static function syncTrackingExpressCompanyFromNumber(ExpressTracking $tracking): void
|
||||||
|
{
|
||||||
|
$num = trim((string) $tracking->tracking_number);
|
||||||
|
if ($num === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$normalized = ExpressTrackService::normalizeExpressCompanyCode(
|
||||||
|
$num,
|
||||||
|
(string) ($tracking->express_company ?? 'auto')
|
||||||
|
);
|
||||||
|
if ($normalized === (string) $tracking->express_company) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$tracking->express_company = $normalized;
|
||||||
|
$tracking->update_time = time();
|
||||||
|
$tracking->save();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存轨迹明细
|
* 保存轨迹明细
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -470,6 +470,17 @@ final class GancaoLogisticsRouteService
|
|||||||
|
|
||||||
$orders = $q->order('id', 'desc')->limit($limit)->select();
|
$orders = $q->order('id', 'desc')->limit($limit)->select();
|
||||||
foreach ($orders as $order) {
|
foreach ($orders as $order) {
|
||||||
|
self::appendSyncOneResult($stats, $order);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{total:int, success:int, failed:int, skipped:int, assistant_cleared:int, assistant_skipped_assign_log:int, assistant_lines:list<string>, details:array<int, array<string,mixed>>} $stats
|
||||||
|
*/
|
||||||
|
private static function appendSyncOneResult(array &$stats, PrescriptionOrder $order): void
|
||||||
|
{
|
||||||
$stats['total']++;
|
$stats['total']++;
|
||||||
try {
|
try {
|
||||||
$r = self::syncOne($order);
|
$r = self::syncOne($order);
|
||||||
@@ -517,7 +528,4 @@ final class GancaoLogisticsRouteService
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $stats;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `zyt_prescription_library` (
|
CREATE TABLE IF NOT EXISTS `zyt_prescription_library` (
|
||||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
`prescription_name` varchar(100) NOT NULL DEFAULT '' COMMENT '处方名称',
|
`prescription_name` varchar(100) NOT NULL DEFAULT '' COMMENT '处方名称',
|
||||||
|
`formula_type` varchar(20) NOT NULL DEFAULT '主方' COMMENT '处方类型:主方、辅方',
|
||||||
`herbs` text COMMENT '药材列表JSON:[{name, dosage}]',
|
`herbs` text COMMENT '药材列表JSON:[{name, dosage}]',
|
||||||
`is_public` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否公开:0-仅自己可见 1-所有人可见',
|
`is_public` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否公开:0-仅自己可见 1-所有人可见',
|
||||||
`creator_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人ID',
|
`creator_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人ID',
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- 处方:辅方用法(用量、袋数、代煎、每天几次、服用天数等)
|
||||||
|
ALTER TABLE `zyt_tcm_prescription`
|
||||||
|
ADD COLUMN `aux_usage` json DEFAULT NULL COMMENT '辅方用法JSON' AFTER `times_per_day`;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- 处方库:处方类型(主方/辅方)
|
||||||
|
ALTER TABLE `zyt_prescription_library`
|
||||||
|
ADD COLUMN `formula_type` varchar(20) NOT NULL DEFAULT '主方' COMMENT '处方类型:主方、辅方' AFTER `prescription_name`;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
-- 消费者开方页「从处方库导入」:补充 tcm.prescriptionLibrary/lists 接口权限
|
||||||
|
-- 执行后需在「权限管理」为相关角色勾选,或依赖 AuthMiddleware 别名(见 PrescriptionLibraryLogic)
|
||||||
|
|
||||||
|
SET @cf_rx_menu_id := (
|
||||||
|
SELECT id FROM zyt_system_menu
|
||||||
|
WHERE type = 'C'
|
||||||
|
AND (
|
||||||
|
component = 'consumer/prescription/index'
|
||||||
|
OR component LIKE '%consumer/prescription/index%'
|
||||||
|
)
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO zyt_system_menu (
|
||||||
|
pid, type, name, icon, sort, perms, paths, component,
|
||||||
|
selected, params, is_cache, is_show, is_disable, create_time, update_time
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
@cf_rx_menu_id, 'A', '处方库列表(开方导入)', '', 54,
|
||||||
|
'tcm.prescriptionLibrary/lists', '', '',
|
||||||
|
'', '', 0, 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
|
||||||
|
FROM DUAL
|
||||||
|
WHERE @cf_rx_menu_id IS NOT NULL
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM zyt_system_menu WHERE perms = 'tcm.prescriptionLibrary/lists');
|
||||||
|
|
||||||
|
SET @wcf_rx_menu_id := (
|
||||||
|
SELECT id FROM zyt_system_menu
|
||||||
|
WHERE type = 'C'
|
||||||
|
AND (
|
||||||
|
component = 'consumer/prescription/list'
|
||||||
|
OR component LIKE '%consumer/prescription/list%'
|
||||||
|
)
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO zyt_system_menu (
|
||||||
|
pid, type, name, icon, sort, perms, paths, component,
|
||||||
|
selected, params, is_cache, is_show, is_disable, create_time, update_time
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
@wcf_rx_menu_id, 'A', '处方库列表(开方导入)', '', 54,
|
||||||
|
'tcm.prescriptionLibrary/lists', '', '',
|
||||||
|
'', '', 0, 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
|
||||||
|
FROM DUAL
|
||||||
|
WHERE @wcf_rx_menu_id IS NOT NULL
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM zyt_system_menu
|
||||||
|
WHERE perms = 'tcm.prescriptionLibrary/lists'
|
||||||
|
AND pid = @wcf_rx_menu_id
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user