Compare commits

...
Author SHA1 Message Date
Your Name 0fb51e12d1 更新 2026-05-19 10:18:00 +08:00
Your Name 4f9494db0b Merge branch 'master' of https://gitee.com/v_cms/zyt 2026-05-18 18:12:23 +08:00
Your Name 4e87b080a0 更新 2026-05-18 18:12:04 +08:00
long 2f3038aba1 Merge branch '20260518-long' 2026-05-18 18:08:46 +08:00
long 131fb7d9c7 Update edit.vue
增加:账户消耗列表-编辑可以修改自媒体
2026-05-18 18:08:35 +08:00
Your Name 762a724204 Merge branch 'master' of https://gitee.com/v_cms/zyt 2026-05-18 18:02:15 +08:00
long 8090e0aab3 Merge branch '20260518-long' 2026-05-18 17:43:25 +08:00
long 9df6330d41 update
修改未服务天数判断条件
2026-05-18 17:42:24 +08:00
Your Name fd63e23eaf 更新 2026-05-18 16:56:00 +08:00
gr c1143f4629 geng
Merge branch 'master' of https://gitee.com/v_cms/zyt
2026-05-18 16:07:03 +08:00
Your Name 149fdffd49 更新 2026-05-18 15:59:22 +08:00
Your Name 439d5a3ec9 更新 2026-05-18 15:57:09 +08:00
long 9e24d49210 Merge branch '20260518-long' 2026-05-18 14:24:52 +08:00
long 90ac43fc82 update
更新自媒体渠道标签
2026-05-18 14:24:41 +08:00
gr 8b6b709987 Merge branch 'master' of https://gitee.com/v_cms/zyt 2026-05-18 11:36:21 +08:00
Your Name fb6ca9ce85 更新 2026-05-18 11:23:01 +08:00
gr 5ce331394d Merge branch 'master' of https://gitee.com/v_cms/zyt
你好
2026-05-14 16:27:49 +08:00
gr 800c6af7f1 Merge branch 'master' of https://gitee.com/v_cms/zyt 2026-05-14 11:31:40 +08:00
gr 876ef0f8a0 Merge branch 'master' of https://gitee.com/v_cms/zyt 2026-05-14 11:02:29 +08:00
gr fdcfa30810 Merge branch 'master' of https://gitee.com/v_cms/zyt 2026-05-12 18:15:10 +08:00
gr 4caa98af3a Merge branch 'master' of https://gitee.com/v_cms/zyt 2026-05-12 11:27:37 +08:00
gr bb87e4fb4c Merge branch 'master' of https://gitee.com/v_cms/zyt 2026-05-12 10:59:56 +08:00
gr 039105580c Mergt branch 'master' of https://gitee.com/v_cms/zyt 2026-05-11 17:49:51 +08:00
gr f47f431bdf 更新 2026-05-11 17:49:38 +08:00
5865 changed files with 821 additions and 415 deletions
+5
View File
@@ -500,6 +500,11 @@ export function prescriptionOrderUpdateAmount(params: { id: number; amount: numb
return request.post({ url: '/tcm.prescriptionOrder/updateAmount', params })
}
/** 设置发货类型:gancao 甘草药房 / direct 洛阳药房 */
export function prescriptionOrderSetShipMode(params: { id: number; ship_mode: 'gancao' | 'direct' }) {
return request.post({ url: '/tcm.prescriptionOrder/setShipMode', params })
}
// ========== 处方库 ==========
// 处方库列表
@@ -277,7 +277,7 @@
:fetch-fun="prescriptionOrderExport"
:params="prescriptionOrderExportParams"
:page-size="pager.size"
export-hint="导出范围与上方筛选一致(履约状态、创建时间及其他条件均会生效)。含「自媒体渠道(挂号渠道来源)」:按诊单关联患者取最近一条挂号的渠道字典与细分。"
export-hint="导出范围与上方筛选一致(履约状态、创建时间及其他条件均会生效)。含「自媒体渠道(挂号渠道来源)」:优先取该单关联处方登记的挂号;无则诊单下同患者挂号取 id 最大的一条(与前台挂号选择的记录一致);业绩侧栏带渠道筛选导出时与同页列表高亮挂号同源。"
/>
</el-form-item>
</el-form>
@@ -495,7 +495,7 @@
>修改单号</el-button>
<el-button
v-if="canShipRow(row)"
v-perms="['tcm.prescriptionOrder/ship', 'tcm.prescriptionOrder/submitGancaoRecipel']"
v-perms="['tcm.prescriptionOrder/ship']"
type="primary"
link
@click="openShip(row)"
@@ -562,6 +562,28 @@
round
class="ml-2"
>甘草 {{ detailData.gancao_reciperl_order_no }}</el-tag>
<div v-if="detailData" class="flex items-center gap-2 ml-4 shrink-0">
<span class="text-sm text-gray-500">发货类型</span>
<el-radio-group
v-perms="['tcm.prescriptionOrder/setShipMode']"
:model-value="normalizeShipMode(detailData.ship_mode)"
size="small"
:disabled="shipModeSaving || !canEditShipMode(detailData)"
@change="onDetailShipModeChange"
>
<el-radio-button label="gancao">甘草药房</el-radio-button>
<el-radio-button
label="direct"
:disabled="isShipModeLockedToGancao(detailData)"
>洛阳药房</el-radio-button>
</el-radio-group>
<el-tag
v-if="!hasSetShipModePerm"
:type="normalizeShipMode(detailData.ship_mode) === 'direct' ? 'warning' : 'success'"
effect="plain"
round
>{{ shipModeLabel(detailData.ship_mode) }}</el-tag>
</div>
</div>
<div v-if="detailData" class="flex items-center gap-2 mr-6">
<el-button
@@ -606,7 +628,7 @@
</el-button>
<el-button
v-if="canShipRow(detailData)"
v-perms="['tcm.prescriptionOrder/ship', 'tcm.prescriptionOrder/submitGancaoRecipel']"
v-perms="['tcm.prescriptionOrder/ship']"
type="primary"
size="small"
plain
@@ -1917,15 +1939,6 @@
:close-on-click-modal="false"
>
<el-alert
v-if="shipForm.ship_mode === 'gancao'"
title="甘草药方由甘草侧履约配送,无需填写快递单号。点击下方按钮将提交处方至甘草药管家(与「上传药方」相同)。"
type="info"
:closable="false"
show-icon
class="mb-4"
/>
<el-alert
v-else
title="确认后订单状态将变更为「已发货」,请核对快递信息无误。"
type="warning"
:closable="false"
@@ -1934,38 +1947,34 @@
/>
<el-form v-loading="shipSaving" label-width="90px" class="pr-2" @submit.prevent="submitShip">
<el-form-item label="发货方式">
<el-radio-group v-model="shipForm.ship_mode">
<el-radio label="gancao">甘草药方发</el-radio>
<el-radio label="direct">药房直发</el-radio>
</el-radio-group>
<el-tag
:type="normalizeShipMode(shipForm.ship_mode) === 'direct' ? 'warning' : 'success'"
effect="plain"
size="default"
>{{ shipDialogModeDisplay }}</el-tag>
<span class="text-xs text-gray-400 ml-2">请在订单详情顶部发货类型中设置此处不可修改</span>
</el-form-item>
<el-form-item label="承运商">
<el-select v-model="shipForm.express_company" class="w-full">
<el-option label="自动识别" value="auto" />
<el-option label="顺丰速运" value="sf" />
<el-option label="京东快递" value="jd" />
<el-option label="极兔速递" value="jt" />
</el-select>
</el-form-item>
<el-form-item label="快递单号">
<el-input
v-model="shipForm.tracking_number"
maxlength="80"
placeholder="请输入快递单号"
clearable
/>
</el-form-item>
<template v-if="shipForm.ship_mode === 'direct'">
<el-form-item label="承运商">
<el-select v-model="shipForm.express_company" class="w-full">
<el-option label="自动识别" value="auto" />
<el-option label="顺丰速运" value="sf" />
<el-option label="京东快递" value="jd" />
<el-option label="极兔速递" value="jt" />
</el-select>
</el-form-item>
<el-form-item label="快递单号">
<el-input
v-model="shipForm.tracking_number"
maxlength="80"
placeholder="请输入快递单号"
clearable
/>
</el-form-item>
</template>
</el-form>
<template #footer>
<el-button @click="shipVisible = false">取消</el-button>
<el-button
type="primary"
:loading="shipSaving || (shipForm.ship_mode === 'gancao' && gancaoSubmitId === shipRowId)"
@click="submitShip"
>
{{ shipForm.ship_mode === 'gancao' ? '确认并上传药方' : '确认发货' }}
<el-button type="primary" :loading="shipSaving" @click="submitShip">
确认发货
</el-button>
</template>
</el-dialog>
@@ -2586,6 +2595,7 @@ import {
prescriptionOrderShip,
prescriptionOrderLogs,
prescriptionOrderUpdateAmount,
prescriptionOrderSetShipMode,
prescriptionOrderAddPayOrder,
prescriptionOrderComplete,
prescriptionOrderRevokeRxAudit,
@@ -2604,7 +2614,6 @@ import { getDictData } from '@/api/app'
import { deptAll } from '@/api/org/department'
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
import { hasPermission } from '@/utils/perm'
import { ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import useUserStore from '@/stores/modules/user'
@@ -3380,6 +3389,70 @@ function canShipRow(row: { fulfillment_status?: number }) {
return Number(row.fulfillment_status) === 2
}
type ShipMode = 'gancao' | 'direct'
const hasSetShipModePerm = computed(() => {
const p = userStore.perms || []
return p.includes('*') || p.includes('tcm.prescriptionOrder/setShipMode')
})
function normalizeShipMode(v: unknown): ShipMode {
return String(v || '').toLowerCase() === 'direct' ? 'direct' : 'gancao'
}
function shipModeLabel(v: unknown): string {
return normalizeShipMode(v) === 'direct' ? '洛阳药房' : '甘草药房'
}
function isShipModeLockedToGancao(row: { gancao_reciperl_order_no?: string | null }) {
return String(row.gancao_reciperl_order_no || '').trim() !== ''
}
function canEditShipMode(row: { fulfillment_status?: number }) {
const fs = Number(row.fulfillment_status)
return fs !== 3 && fs !== 4
}
const shipModeSaving = ref(false)
function isSamePrescriptionOrderId(a: unknown, b: unknown) {
const na = Number(a)
const nb = Number(b)
return na > 0 && na === nb
}
const shipDialogModeDisplay = computed(() => {
return normalizeShipMode(shipForm.ship_mode) === 'direct' ? '洛阳药房直发' : '甘草药房直发'
})
async function onDetailShipModeChange(mode: string | number | boolean | undefined) {
const row = detailData.value
if (!row?.id) return
const next = normalizeShipMode(mode)
const prev = normalizeShipMode(row.ship_mode)
if (next === prev) return
if (!canEditShipMode(row)) {
feedback.msgWarning('当前订单状态不可修改发货类型')
return
}
shipModeSaving.value = true
try {
await prescriptionOrderSetShipMode({ id: row.id, ship_mode: next })
if (detailData.value) detailData.value.ship_mode = next
if (shipVisible.value && isSamePrescriptionOrderId(shipRowId.value, row.id)) {
shipForm.ship_mode = next
}
await refreshCurrentPrescriptionOrderDetail()
feedback.msgSuccess('发货类型已保存')
getLists()
void fetchLogs(row.id)
} catch {
/* 拦截器已提示 */
} finally {
shipModeSaving.value = false
}
}
function canAddPayOrderRow(row: { fulfillment_status?: number }) {
// (5) / (6)
const fs = Number(row.fulfillment_status)
@@ -4761,33 +4834,32 @@ const shipForm = reactive({
tracking_number: ''
})
function openShip(row: { id: number; express_company?: unknown; tracking_number?: unknown; ship_mode?: unknown }) {
shipRowId.value = row.id
const tn = String(row.tracking_number ?? '').trim()
const sm = String(row.ship_mode ?? '').trim()
if (sm === 'direct' || sm === 'gancao') {
shipForm.ship_mode = sm
} else if (tn) {
//
shipForm.ship_mode = 'direct'
} else {
shipForm.ship_mode = 'gancao'
function resolveShipModeForRow(row: { id: number; ship_mode?: unknown }) {
if (isSamePrescriptionOrderId(detailData.value?.id, row.id)) {
return normalizeShipMode(detailData.value!.ship_mode)
}
return normalizeShipMode(row.ship_mode)
}
function openShip(row: { id: number; express_company?: unknown; tracking_number?: unknown; ship_mode?: unknown }) {
shipRowId.value = Number(row.id)
shipForm.ship_mode = resolveShipModeForRow(row)
shipForm.express_company = String(row.express_company || 'auto') || 'auto'
shipForm.tracking_number = String(row.tracking_number || '')
shipVisible.value = true
}
async function submitShip() {
if (shipForm.ship_mode !== 'direct') {
if (!hasPermission(['tcm.prescriptionOrder/submitGancaoRecipel'])) {
feedback.msgError('暂无上传甘草药方权限,请改用「药房直发」并填写快递单号,或联系管理员开通权限')
watch(
() => detailData.value?.ship_mode,
(v) => {
if (!shipVisible.value || !shipRowId.value || !isSamePrescriptionOrderId(detailData.value?.id, shipRowId.value)) {
return
}
shipVisible.value = false
await confirmSubmitGancaoRecipel({ id: shipRowId.value })
return
shipForm.ship_mode = normalizeShipMode(v)
}
)
async function submitShip() {
if (!shipForm.tracking_number.trim()) {
feedback.msgError('请先填写快递单号再确认发货')
return
@@ -4796,7 +4868,7 @@ async function submitShip() {
try {
await prescriptionOrderShip({
id: shipRowId.value,
ship_mode: 'direct',
ship_mode: normalizeShipMode(shipForm.ship_mode),
express_company: shipForm.express_company || 'auto',
tracking_number: shipForm.tracking_number.trim()
})
+39 -12
View File
@@ -22,16 +22,25 @@
<el-select
v-model="formData.media_channel_code"
placeholder="请选择自媒体渠道"
class="w-full"
class="account-cost-channel-select w-full"
filterable
:disabled="mode === 'edit'"
>
<el-option
v-for="item in mediaChannelOptions"
:key="item.code"
:label="item.name"
:value="item.code"
/>
<el-option-group
v-for="g in mediaChannelGroups"
:key="g.group_name"
:label="g.group_name"
>
<el-option
v-for="ch in g.channels"
:key="ch.channel_code"
:label="ch.channel_name"
:value="ch.channel_code"
>
<div class="channel-opt-row">
<span class="opt-name">{{ ch.channel_name }}</span>
</div>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item label="部门" prop="dept_id">
@@ -78,9 +87,9 @@ import type { FormInstance } from 'element-plus'
import { accountCostAdd, accountCostDetail, accountCostEdit } from '@/api/finance'
import Popup from '@/components/popup/index.vue'
interface MediaChannelOption {
code: string
name: string
interface MediaChannelGroup {
group_name: string
channels: { channel_code: string; channel_name: string }[]
}
interface DeptOption {
@@ -90,7 +99,7 @@ interface DeptOption {
}
const props = defineProps<{
mediaChannelOptions: MediaChannelOption[]
mediaChannelGroups: MediaChannelGroup[]
deptOptions: DeptOption[]
defaultMediaChannelCode: string
}>()
@@ -197,3 +206,21 @@ defineExpose({
getDetail,
})
</script>
<style scoped lang="scss">
.account-cost-channel-select {
:deep(.channel-opt-row) {
display: flex;
align-items: center;
width: 100%;
min-width: 0;
padding-right: 4px;
}
:deep(.opt-name) {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
}
</style>
+63 -9
View File
@@ -27,14 +27,25 @@
placeholder="筛选全部渠道"
clearable
filterable
class="w-[220px]"
class="account-cost-channel-select w-[220px]"
>
<el-option
v-for="item in mediaChannelOptions"
:key="item.code"
:label="item.name"
:value="item.code"
/>
<el-option label="全部" value="" />
<el-option-group
v-for="g in mediaChannelGroups"
:key="g.group_name"
:label="g.group_name"
>
<el-option
v-for="ch in g.channels"
:key="ch.channel_code"
:label="ch.channel_name"
:value="ch.channel_code"
>
<div class="channel-opt-row">
<span class="opt-name">{{ ch.channel_name }}</span>
</div>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item label="部门">
@@ -117,7 +128,7 @@
<edit-popup
v-if="showEdit"
ref="editRef"
:media-channel-options="mediaChannelOptions"
:media-channel-groups="mediaChannelGroups"
:dept-options="deptOptions"
:default-media-channel-code="defaultMediaChannelCode"
@success="getLists"
@@ -133,6 +144,11 @@ import feedback from '@/utils/feedback'
import EditPopup from './edit.vue'
interface MediaChannelGroup {
group_name: string
channels: { channel_code: string; channel_name: string }[]
}
interface MediaChannelOption {
code: string
name: string
@@ -160,7 +176,7 @@ const { pager, getLists, resetPage } = usePaging({
params: queryParams,
})
const mediaChannelOptions = computed<MediaChannelOption[]>(() => {
const mediaChannelOptionsFlat = computed<MediaChannelOption[]>(() => {
const options = pager.extend.media_channel_options
if (!Array.isArray(options)) return []
@@ -170,6 +186,26 @@ const mediaChannelOptions = computed<MediaChannelOption[]>(() => {
}))
})
const mediaChannelGroups = computed<MediaChannelGroup[]>(() => {
const groups = pager.extend.media_channel_groups
if (Array.isArray(groups) && groups.length > 0) {
return groups as MediaChannelGroup[]
}
const flat = mediaChannelOptionsFlat.value
if (!flat.length) {
return []
}
return [
{
group_name: '渠道',
channels: flat.map(item => ({
channel_code: item.code,
channel_name: item.name,
})),
},
]
})
const deptOptions = computed<DeptOption[]>(() => {
const options = pager.extend.dept_options
return Array.isArray(options) ? options : []
@@ -213,3 +249,21 @@ const handleReset = () => {
getLists()
</script>
<style scoped lang="scss">
.account-cost-channel-select {
:deep(.channel-opt-row) {
display: flex;
align-items: center;
width: 100%;
min-width: 0;
padding-right: 4px;
}
:deep(.opt-name) {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
}
</style>
+1 -1
View File
@@ -257,7 +257,7 @@
<template #default="{ row }">
<el-tooltip
v-if="row.last_blood_record_at"
:content="`最近一次血糖打卡:${row.last_blood_record_at}`"
:content="`最近一次健康打卡:${row.last_blood_record_at}`"
placement="top"
>
<span class="unserved-days" :class="unservedDaysClass(row.unserved_days)">
+2 -2
View File
@@ -17,14 +17,14 @@
</span>
<el-tooltip
v-if="lastBloodAt"
:content="`最近一次血糖打卡:${lastBloodAt}`"
:content="`最近一次健康打卡:${lastBloodAt}`"
placement="bottom"
>
<span :class="['unserved-badge', unservedDaysClass(unservedDays)]">
未服务 {{ unservedDays ?? '—' }}
</span>
</el-tooltip>
<span v-else class="unserved-badge unserved-muted">从未打卡血糖</span>
<span v-else class="unserved-badge unserved-muted">从未打卡</span>
</div>
</div>
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
+19
View File
@@ -124,6 +124,25 @@ class PrescriptionOrderController extends BaseAdminController
return $this->success('修改成功');
}
/**
* 设置发货类型(甘草 / 洛阳)
*/
public function setShipMode()
{
$params = (new PrescriptionOrderValidate())->post()->goCheck('setShipMode');
$result = PrescriptionOrderLogic::setShipMode(
(int) $params['id'],
(string) ($params['ship_mode'] ?? 'gancao'),
$this->adminId,
$this->adminInfo
);
if ($result === false) {
return $this->fail(PrescriptionOrderLogic::getError());
}
return $this->success('已保存', $result);
}
/**
* 修改关联处方的患者姓名与手机号(订单详情场景)
*/
View File
View File
Regular → Executable
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File

Some files were not shown because too many files have changed in this diff Show More