Files
zyt/admin/src/views/first_visit/my_patients/components/OrderActionHost.vue
T
2026-08-05 11:08:02 +08:00

685 lines
26 KiB
Vue

<template>
<prescription-order-detail-drawer
ref="detailDrawerRef"
readonly
append-to-body
:detail-loader="myPatientOrderDetail"
:load-related-data="false"
>
<template #header-actions="{ detail }">
<el-dropdown
v-if="myPatientOrderActions(detail).length"
trigger="click"
@command="(command) => openAction(detail, command as MyPatientOrderAction)"
>
<el-button type="primary" size="small" plain>
订单操作<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="action in myPatientOrderActions(detail)"
:key="action.key"
:command="action.key"
:class="{ 'danger-menu-item': action.danger }"
>
{{ action.label }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</prescription-order-detail-drawer>
<el-dialog v-model="auditVisible" :title="auditTitle" width="480px" append-to-body destroy-on-close>
<el-alert
title="通过时审核意见可不填;驳回时必须填写明确原因。"
type="warning"
:closable="false"
show-icon
class="mb-4"
/>
<el-input
v-model="auditRemark"
type="textarea"
:rows="4"
maxlength="500"
show-word-limit
placeholder="请输入审核意见"
/>
<template #footer>
<el-button @click="auditVisible = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submitAudit('approve')">同意通过</el-button>
<el-button type="danger" :loading="submitting" @click="submitAudit('reject')">驳回</el-button>
</template>
</el-dialog>
<el-dialog v-model="trackVisible" title="修改快递单号" width="440px" append-to-body destroy-on-close>
<el-form label-width="92px" @submit.prevent="submitTrack">
<el-form-item label="承运商">
<el-select v-model="trackForm.express_company" class="w-full">
<el-option v-for="item in expressOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="快递单号" required>
<el-input v-model="trackForm.tracking_number" maxlength="80" clearable placeholder="请输入快递单号" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="trackVisible = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submitTrack">保存</el-button>
</template>
</el-dialog>
<el-dialog v-model="shipVisible" title="确认发货" width="460px" append-to-body destroy-on-close>
<el-alert
title="确认后订单将进入“已发货”,请先核对发货方式和快递信息。"
type="warning"
:closable="false"
show-icon
class="mb-4"
/>
<el-form label-width="92px" @submit.prevent="submitShip">
<el-form-item label="发货方式">
<el-tag :type="shipForm.ship_mode === 'direct' ? 'warning' : 'success'" effect="plain">
{{ shipForm.ship_mode === 'direct' ? '洛阳药房' : '甘草药房' }}
</el-tag>
</el-form-item>
<el-form-item label="承运商">
<el-select v-model="shipForm.express_company" class="w-full">
<el-option v-for="item in expressOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="快递单号" required>
<el-input v-model="shipForm.tracking_number" maxlength="80" clearable placeholder="请输入快递单号" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="shipVisible = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submitShip">确认发货</el-button>
</template>
</el-dialog>
<el-dialog v-model="editVisible" title="编辑订单" width="680px" append-to-body destroy-on-close>
<el-form v-loading="editLoading" label-width="104px" class="order-edit-form">
<div class="form-grid">
<el-form-item label="收货人" required>
<el-input v-model="editForm.recipient_name" maxlength="50" />
</el-form-item>
<el-form-item label="收货手机" required>
<el-input v-model="editForm.recipient_phone" maxlength="20" />
</el-form-item>
<el-form-item label="费用类别" required>
<el-select v-model="editForm.fee_type" class="w-full">
<el-option v-for="item in feeTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="订单金额" required>
<el-input-number v-model="editForm.amount" :min="0" :precision="2" :step="10" class="w-full" />
</el-form-item>
</div>
<el-form-item label="收货地址" required>
<el-input v-model="editForm.shipping_address" maxlength="500" />
</el-form-item>
<div class="form-grid">
<el-form-item label="承运商">
<el-select v-model="editForm.express_company" class="w-full">
<el-option v-for="item in expressOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="快递单号">
<el-input v-model="editForm.tracking_number" maxlength="80" clearable />
</el-form-item>
</div>
<el-form-item label="医助备注">
<el-input v-model="editForm.remark_assistant" type="textarea" :rows="2" maxlength="500" show-word-limit />
</el-form-item>
<el-form-item label="药房备注">
<el-input v-model="editForm.remark_extra" type="textarea" :rows="2" maxlength="500" show-word-limit />
</el-form-item>
<el-alert
title="保存后会沿用原订单编辑规则;非已发货订单的支付审核将重新变为待审核。"
type="info"
:closable="false"
show-icon
/>
</el-form>
<template #footer>
<el-button @click="editVisible = false">取消</el-button>
<el-button type="primary" :loading="submitting" :disabled="editLoading" @click="submitEdit">保存</el-button>
</template>
</el-dialog>
<el-dialog v-model="payVisible" title="补齐支付单" width="520px" append-to-body destroy-on-close>
<el-alert
:title="`订单金额 ¥${money(actionRow.amount)},已关联支付 ¥${money(actionRow.linked_pay_paid_total)}`"
type="info"
:closable="false"
show-icon
class="mb-4"
/>
<el-form label-width="108px">
<el-form-item label="支付单类型">
<el-select v-model="payForm.order_type" class="w-full">
<el-option v-for="item in payTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="创建方式">
<el-radio-group v-model="payForm.pay_create_type">
<el-radio value="fubei">付呗支付</el-radio>
<el-radio value="express_cod">快递代收</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="补齐金额" required>
<el-input-number v-model="payForm.pay_amount" :min="0" :precision="2" :step="10" class="w-full" />
</el-form-item>
<el-form-item label="支付备注">
<el-input v-model="payForm.pay_remark" type="textarea" :rows="3" maxlength="200" show-word-limit />
</el-form-item>
<el-form-item label="完单申请">
<el-switch v-model="payForm.completion_request" :active-value="1" :inactive-value="0" />
<span class="form-tip">同时提交完单申请</span>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="payVisible = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submitPayOrder">确认新增</el-button>
</template>
</el-dialog>
<el-dialog v-model="completeVisible" title="完成订单" width="480px" append-to-body destroy-on-close>
<el-alert
title="请选择真实履约结果。退款必须使用单独的“退款”操作。"
type="warning"
:closable="false"
show-icon
class="mb-4"
/>
<el-form label-width="100px">
<el-form-item label="结案状态" required>
<el-select v-model="completeStatus" class="w-full">
<el-option v-for="item in completeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="completeVisible = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submitComplete">确认完成</el-button>
</template>
</el-dialog>
<el-dialog v-model="refundVisible" title="订单退款" width="500px" append-to-body destroy-on-close>
<el-alert
title="退款会同步更新关联支付单和订单金额;退款金额留空时按系统计算的最大可退金额处理。"
type="warning"
:closable="false"
show-icon
class="mb-4"
/>
<el-form label-width="100px">
<el-form-item label="退款原因" required>
<el-input v-model="refundForm.reason" type="textarea" :rows="3" maxlength="500" show-word-limit />
</el-form-item>
<el-form-item label="退款金额">
<el-input-number
v-model="refundForm.refund_amount"
:min="0"
:precision="2"
:step="10"
class="w-full"
placeholder="留空则退最大可退金额"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="refundVisible = false">取消</el-button>
<el-button type="danger" :loading="submitting" @click="submitRefund">确认退款</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import { ArrowDown } from '@element-plus/icons-vue'
import { ElMessageBox } from 'element-plus'
import feedback from '@/utils/feedback'
import PrescriptionOrderDetailDrawer from '@/views/consumer/prescription/components/PrescriptionOrderDetailDrawer.vue'
import {
myPatientOrderAddPayOrder,
myPatientOrderAuditPayment,
myPatientOrderAuditPrescription,
myPatientOrderComplete,
myPatientOrderDdcode,
myPatientOrderDetail,
myPatientOrderEdit,
myPatientOrderRefund,
myPatientOrderRevokePayAudit,
myPatientOrderRevokeRxAudit,
myPatientOrderShip,
myPatientOrderUploadToPharmacy,
myPatientOrderWithdraw
} from '@/api/first_visit'
import { myPatientOrderActions, type MyPatientOrderAction } from './order-actions'
const emit = defineEmits<{ changed: [] }>()
const detailDrawerRef = ref<InstanceType<typeof PrescriptionOrderDetailDrawer>>()
const actionRow = ref<Record<string, any>>({})
const submitting = ref(false)
const expressOptions = [
{ label: '自动识别', value: 'auto' },
{ label: '顺丰速运', value: 'sf' },
{ label: '京东快递', value: 'jd' },
{ label: '极兔速递', value: 'jt' }
]
const feeTypeOptions = [
{ label: '挂号', value: 1 },
{ label: '问诊', value: 2 },
{ label: '药品', value: 3 },
{ label: '首付', value: 4 },
{ label: '尾款', value: 5 },
{ label: '其他', value: 6 },
{ label: '全部', value: 7 },
{ label: '代收', value: 8 }
]
const payTypeOptions = [
{ label: '药品', value: 3 },
{ label: '尾款', value: 5 },
{ label: '其他', value: 6 }
]
const completeOptions = [
{ label: '已完成', value: 3 },
{ label: '进行中', value: 7 },
{ label: '暂不制药', value: 8 },
{ label: '拒收', value: 9 },
{ label: '保留药方', value: 11 },
{ label: '制药缓发', value: 12 }
]
const auditVisible = ref(false)
const auditKind = ref<'prescription' | 'payment'>('prescription')
const auditRemark = ref('')
const auditTitle = computed(() => auditKind.value === 'prescription' ? '处方业务审核' : '关联支付单审核')
const trackVisible = ref(false)
const trackForm = reactive({ express_company: 'auto', tracking_number: '' })
const shipVisible = ref(false)
const shipForm = reactive<{ ship_mode: 'gancao' | 'direct'; express_company: string; tracking_number: string }>({
ship_mode: 'gancao',
express_company: 'auto',
tracking_number: ''
})
const editVisible = ref(false)
const editLoading = ref(false)
const editSource = ref<Record<string, any>>({})
const editForm = reactive({
recipient_name: '',
recipient_phone: '',
shipping_address: '',
fee_type: 3,
amount: 0,
express_company: 'auto',
tracking_number: '',
remark_assistant: '',
remark_extra: ''
})
const payVisible = ref(false)
const payForm = reactive<{
order_type: number
pay_amount: number
pay_remark: string
completion_request: number
pay_create_type: 'fubei' | 'express_cod'
}>({ order_type: 3, pay_amount: 0, pay_remark: '', completion_request: 0, pay_create_type: 'fubei' })
const completeVisible = ref(false)
const completeStatus = ref(3)
const refundVisible = ref(false)
const refundForm = reactive<{ reason: string; refund_amount: number | undefined }>({
reason: '',
refund_amount: undefined
})
function money(value: unknown) {
return Number(value || 0).toFixed(2)
}
function normalizeShipMode(value: unknown): 'gancao' | 'direct' {
return String(value || '').toLowerCase() === 'direct' ? 'direct' : 'gancao'
}
function openDetail(row: Record<string, any>) {
detailDrawerRef.value?.open(Number(row.id))
}
async function fetchDetail(id: number) {
const response: any = await myPatientOrderDetail({ id })
return response?.data ?? response ?? null
}
async function openEdit(row: Record<string, any>) {
actionRow.value = row
editVisible.value = true
editLoading.value = true
try {
const detail = await fetchDetail(Number(row.id))
if (!detail) throw new Error('订单详情加载失败')
editSource.value = detail
editForm.recipient_name = String(detail.recipient_name || '')
editForm.recipient_phone = String(detail.recipient_phone || '')
editForm.shipping_address = String(detail.shipping_address || '')
editForm.fee_type = Number(detail.fee_type || 3)
editForm.amount = Number(detail.amount || 0)
editForm.express_company = String(detail.express_company || 'auto') || 'auto'
editForm.tracking_number = String(detail.tracking_number || '')
editForm.remark_assistant = String(detail.remark_assistant || '')
editForm.remark_extra = String(detail.remark_extra || '')
} catch (error: any) {
feedback.msgError(error?.message || '订单详情加载失败')
editVisible.value = false
} finally {
editLoading.value = false
}
}
function openAction(row: Record<string, any>, action: MyPatientOrderAction) {
actionRow.value = row
switch (action) {
case 'edit':
void openEdit(row)
break
case 'audit_prescription':
case 'audit_payment':
auditKind.value = action === 'audit_prescription' ? 'prescription' : 'payment'
auditRemark.value = ''
auditVisible.value = true
break
case 'revoke_rx_audit':
void confirmRevokeAudit('prescription')
break
case 'revoke_pay_audit':
void confirmRevokeAudit('payment')
break
case 'ddcode':
trackForm.express_company = String(row.express_company || 'auto') || 'auto'
trackForm.tracking_number = String(row.tracking_number || '')
trackVisible.value = true
break
case 'ship':
shipForm.ship_mode = normalizeShipMode(row.ship_mode)
shipForm.express_company = String(row.express_company || 'auto') || 'auto'
shipForm.tracking_number = String(row.tracking_number || '')
shipVisible.value = true
break
case 'add_pay_order': {
const diff = Math.max(0, Number(row.amount || 0) - Number(row.linked_pay_paid_total || 0))
payForm.order_type = 3
payForm.pay_amount = Number(diff.toFixed(2))
payForm.pay_remark = ''
payForm.completion_request = 0
payForm.pay_create_type = 'fubei'
payVisible.value = true
break
}
case 'complete':
completeStatus.value = 3
completeVisible.value = true
break
case 'refund':
refundForm.reason = ''
refundForm.refund_amount = undefined
refundVisible.value = true
break
case 'withdraw':
void confirmWithdraw()
break
case 'upload_pharmacy':
void confirmUploadPharmacy()
break
}
}
async function changed(message: string) {
feedback.msgSuccess(message)
emit('changed')
await detailDrawerRef.value?.refreshIfCurrent(actionRow.value.id)
}
async function submitAudit(action: 'approve' | 'reject') {
if (action === 'reject' && !auditRemark.value.trim()) {
feedback.msgError('驳回时必须填写审核意见')
return
}
submitting.value = true
try {
const params = { id: Number(actionRow.value.id), action, remark: auditRemark.value.trim() }
if (auditKind.value === 'prescription') await myPatientOrderAuditPrescription(params)
else await myPatientOrderAuditPayment(params)
auditVisible.value = false
await changed(action === 'approve' ? '审核通过成功' : '订单已驳回')
} catch {
// 请求拦截器已提示
} finally {
submitting.value = false
}
}
async function confirmRevokeAudit(kind: 'prescription' | 'payment') {
try {
await ElMessageBox.confirm(
`确认撤回${kind === 'prescription' ? '处方' : '支付单'}审核并恢复为待审核吗?`,
'撤回审核',
{ type: 'warning', confirmButtonText: '确认撤回', cancelButtonText: '取消' }
)
if (kind === 'prescription') await myPatientOrderRevokeRxAudit({ id: Number(actionRow.value.id) })
else await myPatientOrderRevokePayAudit({ id: Number(actionRow.value.id) })
await changed('审核已撤回')
} catch {
// 取消确认或请求拦截器已处理
}
}
async function submitTrack() {
if (!trackForm.tracking_number.trim()) {
feedback.msgError('请填写快递单号')
return
}
submitting.value = true
try {
await myPatientOrderDdcode({
id: Number(actionRow.value.id),
express_company: trackForm.express_company || 'auto',
tracking_number: trackForm.tracking_number.trim()
})
trackVisible.value = false
await changed('快递单号已保存')
} catch {
// 请求拦截器已提示
} finally {
submitting.value = false
}
}
async function submitShip() {
if (!shipForm.tracking_number.trim()) {
feedback.msgError('请填写快递单号')
return
}
submitting.value = true
try {
await myPatientOrderShip({
id: Number(actionRow.value.id),
ship_mode: shipForm.ship_mode,
express_company: shipForm.express_company || 'auto',
tracking_number: shipForm.tracking_number.trim()
})
shipVisible.value = false
await changed('确认发货成功')
} catch {
// 请求拦截器已提示
} finally {
submitting.value = false
}
}
async function submitEdit() {
if (!editForm.recipient_name.trim() || !editForm.recipient_phone.trim() || !editForm.shipping_address.trim()) {
feedback.msgError('请完整填写收货人、收货手机和收货地址')
return
}
if (Number(editForm.amount) < 0) {
feedback.msgError('订单金额不能为负数')
return
}
const source = editSource.value
submitting.value = true
try {
await myPatientOrderEdit({
id: Number(actionRow.value.id),
recipient_name: editForm.recipient_name.trim(),
recipient_phone: editForm.recipient_phone.trim(),
shipping_province: source.shipping_province || '',
shipping_city: source.shipping_city || '',
shipping_district: source.shipping_district || '',
shipping_address: editForm.shipping_address.trim(),
is_follow_up: Number(source.is_follow_up || 0),
medication_days: source.medication_days,
dose_unit: source.dose_unit || '剂',
dose_count: Number(source.dose_count || 1),
prev_staff: source.prev_staff || '',
service_channel: source.service_channel || '',
service_package: source.service_package || '',
tracking_number: editForm.tracking_number.trim(),
express_company: editForm.express_company || 'auto',
fee_type: Number(editForm.fee_type),
amount: Number(editForm.amount),
remark_extra: editForm.remark_extra.trim(),
remark_assistant: editForm.remark_assistant.trim(),
pay_order_ids: Array.isArray(source.pay_order_ids) ? source.pay_order_ids : []
})
editVisible.value = false
await changed('订单保存成功')
} catch {
// 请求拦截器已提示
} finally {
submitting.value = false
}
}
async function submitPayOrder() {
if (!(Number(payForm.pay_amount) > 0)) {
feedback.msgError('补齐金额必须大于 0')
return
}
submitting.value = true
try {
await myPatientOrderAddPayOrder({
id: Number(actionRow.value.id),
order_type: Number(payForm.order_type),
pay_amount: Number(payForm.pay_amount),
pay_remark: payForm.pay_remark.trim(),
completion_request: Number(payForm.completion_request),
pay_create_type: payForm.pay_create_type
})
payVisible.value = false
await changed('支付单已新增,等待支付审核')
} catch {
// 请求拦截器已提示
} finally {
submitting.value = false
}
}
async function submitComplete() {
submitting.value = true
try {
await myPatientOrderComplete({ id: Number(actionRow.value.id), fulfillment_status: Number(completeStatus.value) })
completeVisible.value = false
await changed('订单状态已更新')
} catch {
// 请求拦截器已提示
} finally {
submitting.value = false
}
}
async function submitRefund() {
if (!refundForm.reason.trim()) {
feedback.msgError('请填写退款原因')
return
}
submitting.value = true
try {
await myPatientOrderRefund({
id: Number(actionRow.value.id),
reason: refundForm.reason.trim(),
refund_amount: refundForm.refund_amount === undefined ? undefined : Number(refundForm.refund_amount)
})
refundVisible.value = false
await changed('退款成功')
} catch {
// 请求拦截器已提示
} finally {
submitting.value = false
}
}
async function confirmWithdraw() {
try {
await ElMessageBox.confirm('确认撤回该订单吗?撤回后当前订单将结束。', '撤回订单', {
type: 'warning',
confirmButtonText: '确认撤回',
cancelButtonText: '取消'
})
await myPatientOrderWithdraw({ id: Number(actionRow.value.id) })
await changed('订单已撤回')
} catch {
// 取消确认或请求拦截器已处理
}
}
async function confirmUploadPharmacy() {
try {
await ElMessageBox.confirm('确认将该订单上传到订单设定的药房吗?', '上传药房', {
type: 'warning',
confirmButtonText: '确认上传',
cancelButtonText: '取消'
})
await myPatientOrderUploadToPharmacy({ id: Number(actionRow.value.id) })
await changed('药方上传成功')
} catch {
// 取消确认或请求拦截器已处理
}
}
defineExpose({ openDetail, openAction })
</script>
<style scoped lang="scss">
.form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0 14px;
}
.form-tip {
margin-left: 10px;
color: #8a95a6;
font-size: 12px;
}
:global(.danger-menu-item) {
color: var(--el-color-danger) !important;
}
@media (max-width: 720px) {
.form-grid {
grid-template-columns: 1fr;
}
}
</style>