1576 lines
60 KiB
Vue
1576 lines
60 KiB
Vue
<!-- 订单列表 -->
|
||
<template>
|
||
<div class="order-list">
|
||
<el-card class="!border-none" shadow="never">
|
||
<!-- 搜索表单 -->
|
||
<el-form class="ls-form" :model="queryParams" inline>
|
||
<el-form-item class="w-[280px]" label="订单号">
|
||
<el-input
|
||
v-model="queryParams.order_no"
|
||
placeholder="请输入订单号"
|
||
clearable
|
||
@keyup.enter="handleSearch"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item class="w-[280px]" label="患者信息">
|
||
<el-input
|
||
v-model="queryParams.patient_keyword"
|
||
placeholder="患者姓名/手机号"
|
||
clearable
|
||
@keyup.enter="handleSearch"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item class="w-[280px]" label="医助">
|
||
<el-select
|
||
v-model="queryParams.assistant_id"
|
||
placeholder="全部医助"
|
||
clearable
|
||
filterable
|
||
class="w-full"
|
||
>
|
||
<el-option
|
||
v-for="item in assistantOptions"
|
||
:key="item.id"
|
||
:label="item.name"
|
||
:value="Number(item.id)"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item class="w-[280px]" label="订单类型">
|
||
<el-select v-model="queryParams.order_type" class="w-full" placeholder="全部">
|
||
<el-option label="全部" value="" />
|
||
<el-option label="挂号费" :value="1" />
|
||
<el-option label="问诊费" :value="2" />
|
||
<el-option label="药品费用" :value="3" />
|
||
<el-option label="首付费用" :value="4" />
|
||
<el-option label="尾款费用" :value="5" />
|
||
<el-option label="其他费用" :value="6" />
|
||
<el-option label="全部费用" :value="7" />
|
||
<el-option label="驼奶费用" :value="8" />
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item class="w-[280px]" label="订单状态">
|
||
<el-select v-model="queryParams.status" class="w-full" placeholder="全部">
|
||
<el-option label="全部" value="" />
|
||
<el-option label="待支付" :value="1" />
|
||
<el-option label="已支付" :value="2" />
|
||
<el-option label="已取消" :value="3" />
|
||
<el-option label="已退款" :value="4" />
|
||
<el-option label="待审核" :value="5" />
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="创建时间">
|
||
<daterange-picker
|
||
v-model:startTime="queryParams.create_time_start"
|
||
v-model:endTime="queryParams.create_time_end"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item>
|
||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||
<el-button @click="handleReset">重置</el-button>
|
||
<el-button
|
||
v-perms="['order.order/lists']"
|
||
@click="statsDialogVisible = true"
|
||
>
|
||
操作统计
|
||
</el-button>
|
||
<export-data
|
||
v-perms="['order.order/export']"
|
||
class="ml-2.5"
|
||
:fetch-fun="orderExport"
|
||
:params="queryParams"
|
||
:page-size="pager.size"
|
||
/>
|
||
<el-button
|
||
v-perms="['order.order/create']"
|
||
type="success"
|
||
class="ml-2.5"
|
||
@click="handleCreate"
|
||
>
|
||
+ 创建支付单
|
||
</el-button>
|
||
<el-button
|
||
v-perms="['order.order/alltongbu']"
|
||
type="warning"
|
||
class="ml-2.5"
|
||
:loading="syncLoading"
|
||
@click="handleSyncWechatWork"
|
||
>
|
||
同步企业微信收款
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- Tab 筛选 + 今日收益 -->
|
||
<el-card class="!border-none mt-4" shadow="never">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<el-tabs v-model="patientAssociationTab" @tab-change="handleTabChange">
|
||
<el-tab-pane label="全部" name="" />
|
||
<el-tab-pane label="待关联" name="pending" />
|
||
<el-tab-pane label="已关联" name="associated" />
|
||
</el-tabs>
|
||
<div class="flex items-center gap-4 text-sm">
|
||
<span class="text-gray-500">今日收益:</span>
|
||
<span class="text-red-500 font-semibold text-lg">¥{{ todayRevenue.amount }}</span>
|
||
<span class="text-gray-400">({{ todayRevenue.count }} 笔)</span>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 数据表格 -->
|
||
<el-card class="!border-none mt-4" shadow="never">
|
||
<div v-perms="['order.order/zhipai']" class="mb-3 flex items-center gap-3">
|
||
<el-button type="primary" :disabled="!selectedOrderIds.length" @click="openAssignDialog">
|
||
将创建人指给医助
|
||
</el-button>
|
||
<span v-if="selectedOrderIds.length" class="text-sm text-gray-500"
|
||
>已选 {{ selectedOrderIds.length }} 条</span
|
||
>
|
||
</div>
|
||
<el-table
|
||
ref="orderTableRef"
|
||
v-loading="pager.loading"
|
||
:data="pager.lists"
|
||
row-key="id"
|
||
size="large"
|
||
@selection-change="handleOrderSelectionChange"
|
||
>
|
||
<el-table-column type="selection" width="48" />
|
||
<el-table-column label="订单号" prop="order_no" min-width="150" />
|
||
|
||
<el-table-column label="患者信息" min-width="150">
|
||
<template #default="{ row }">
|
||
<div v-if="row.patient">{{ row.patient.patient_name }}</div>
|
||
<div v-else class="text-amber-600">待关联</div>
|
||
<div class="text-xs text-gray-400">{{ row.patient?.phone || '-' }}</div>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="创建人" min-width="120">
|
||
<template #default="{ row }">
|
||
<div>{{ row.creator?.name || '-' }}</div>
|
||
<div v-if="row.payee_userid" class="text-xs text-gray-400">收款: {{ row.payee_userid }}</div>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="订单类型" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag :type="getOrderTypeTag(row.order_type)">
|
||
{{ getOrderTypeText(row.order_type) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="金额" width="100">
|
||
<template #default="{ row }">
|
||
<span class="text-red-500 font-semibold">¥{{ row.amount }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="支付方式" width="100">
|
||
<template #default="{ row }">
|
||
{{ getPaymentMethodText(row.payment_method) }}
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="订单状态" width="120">
|
||
<template #default="{ row }">
|
||
<el-tag :type="getStatusTag(row.status)">
|
||
{{ getStatusText(row.status) }}
|
||
</el-tag>
|
||
<el-tag v-if="row.is_exempt === 1" type="warning" size="small" class="ml-1">豁免</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="支付时间" min-width="160">
|
||
<template #default="{ row }">
|
||
{{ row.payment_time || '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="创建时间" min-width="160">
|
||
<template #default="{ row }">
|
||
{{ row.create_time }}
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="操作" width="340" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button
|
||
v-perms="['order.order/detail']"
|
||
type="primary"
|
||
link
|
||
@click="handleDetail(row)"
|
||
>
|
||
详情
|
||
</el-button>
|
||
<el-button
|
||
v-perms="['order.order/edit']"
|
||
type="primary"
|
||
link
|
||
@click="handleEditOrder(row)"
|
||
>
|
||
编辑
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.status === 1 || row.status === 2 || (row.status === 5 && row.payment_method === 'fubei')"
|
||
v-perms="['order.order/split']"
|
||
type="primary"
|
||
link
|
||
@click="openSplitOrder(row)"
|
||
>
|
||
拆分
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
v-perms="['order.order/wxQrcode']"
|
||
link
|
||
@click="handleOrderQrcode(row)"
|
||
>
|
||
小程序码
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.status === 1 || (row.status === 5 && row.payment_method === 'fubei')"
|
||
v-perms="['order.order/pay']"
|
||
type="success"
|
||
link
|
||
@click="handlePay(row)"
|
||
>
|
||
支付
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.status === 2"
|
||
v-perms="['order.order/refund']"
|
||
type="warning"
|
||
link
|
||
@click="handleRefund(row)"
|
||
>
|
||
退款
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.status === 1 || (row.status === 5 && row.payment_method === 'fubei')"
|
||
v-perms="['order.order/cancel']"
|
||
type="danger"
|
||
link
|
||
@click="handleCancel(row)"
|
||
>
|
||
取消
|
||
</el-button>
|
||
<el-button
|
||
v-perms="['order.order/delete']"
|
||
type="danger"
|
||
link
|
||
@click="handleDelete(row)"
|
||
>
|
||
删除
|
||
</el-button>
|
||
<el-button
|
||
v-perms="['order.order/setExempt']"
|
||
:type="row.is_exempt === 1 ? 'warning' : 'default'"
|
||
link
|
||
@click="handleToggleExempt(row)"
|
||
>
|
||
{{ row.is_exempt === 1 ? '取消豁免' : '设为豁免' }}
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<div class="flex justify-end mt-4">
|
||
<pagination v-model="pager" @change="getLists" />
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 详情弹窗 -->
|
||
<el-dialog
|
||
v-model="detailVisible"
|
||
title="订单详情"
|
||
width="820px"
|
||
>
|
||
<el-descriptions :column="2" border v-if="detailData">
|
||
<el-descriptions-item label="订单号">
|
||
{{ detailData.order_no }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="订单状态">
|
||
<el-tag :type="getStatusTag(detailData.status)">
|
||
{{ getStatusText(detailData.status) }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="患者姓名">
|
||
{{ detailData.patient?.patient_name || '待关联' }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="患者电话">
|
||
{{ detailData.patient?.phone || '-' }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="创建人">
|
||
{{ detailData.creator?.name || '-' }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="收款人userid" v-if="detailData.payee_userid">
|
||
{{ detailData.payee_userid }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="付款人external_userid" v-if="detailData.payer_external_userid">
|
||
{{ detailData.payer_external_userid }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="订单类型">
|
||
<el-tag :type="getOrderTypeTag(detailData.order_type)">
|
||
{{ getOrderTypeText(detailData.order_type) }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="订单金额">
|
||
<span class="text-red-500 font-semibold">¥{{ detailData.amount }}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="支付方式">
|
||
{{ getPaymentMethodText(detailData.payment_method) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="支付时间">
|
||
{{ detailData.payment_time || '-' }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="创建时间">
|
||
{{ detailData.create_time }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="备注" :span="2">
|
||
{{ detailData.remark || '无' }}
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
<el-divider content-position="left" v-if="detailData">操作记录</el-divider>
|
||
<p v-if="detailData" class="text-xs text-gray-500 mb-2">谁在什么时间对本单做了哪些操作(数据来自服务器记录)。</p>
|
||
<el-table
|
||
v-if="detailData"
|
||
v-loading="actionLogLoading"
|
||
:data="actionLogList"
|
||
size="small"
|
||
max-height="320"
|
||
>
|
||
<el-table-column label="操作" prop="action_label" min-width="120" show-overflow-tooltip />
|
||
<el-table-column label="操作人" prop="admin_name" min-width="100" />
|
||
<el-table-column label="操作时间" prop="create_time_text" min-width="160" />
|
||
<el-table-column label="说明" prop="summary" min-width="140" show-overflow-tooltip />
|
||
</el-table>
|
||
<div v-if="detailData" class="flex justify-end mt-3">
|
||
<el-pagination
|
||
v-model:current-page="actionLogPage"
|
||
v-model:page-size="actionLogPageSize"
|
||
:total="actionLogTotal"
|
||
:page-sizes="[10, 20, 50]"
|
||
layout="total, sizes, prev, pager, next"
|
||
small
|
||
@size-change="onActionLogSizeChange"
|
||
@current-change="loadOrderActionLogs"
|
||
/>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 按人操作统计(时间范围内) -->
|
||
<el-dialog
|
||
v-model="statsDialogVisible"
|
||
title="支付单操作统计"
|
||
width="520px"
|
||
@opened="onStatsDialogOpen"
|
||
>
|
||
<p v-if="statsRangeText" class="text-xs text-gray-500 mb-3">统计范围:{{ statsRangeText }}</p>
|
||
<el-form class="mb-3" :inline="true" @submit.prevent>
|
||
<el-form-item label="时间范围">
|
||
<el-date-picker
|
||
v-model="statsDateRange"
|
||
type="daterange"
|
||
value-format="YYYY-MM-DD"
|
||
range-separator="至"
|
||
start-placeholder="开始"
|
||
end-placeholder="结束"
|
||
class="!w-[260px]"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" :loading="statsLoading" @click="fetchActionLogStats">查询</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
<el-table v-loading="statsLoading" :data="statsList" size="small" max-height="400">
|
||
<el-table-column label="操作人" prop="admin_name" min-width="120" show-overflow-tooltip />
|
||
<el-table-column label="操作次数" prop="cnt" width="100" align="right" />
|
||
</el-table>
|
||
</el-dialog>
|
||
|
||
<el-dialog v-model="assignDialogVisible" title="变更创建人(医助)" width="440px" destroy-on-close>
|
||
<p class="text-sm text-gray-600 mb-3">
|
||
将 {{ selectedOrderIds.length }} 条支付单的「创建人」改为所选医助,并写入各单操作日志。
|
||
</p>
|
||
<el-select
|
||
v-model="assignForm.assistant_id"
|
||
filterable
|
||
placeholder="选择医助"
|
||
class="w-full"
|
||
>
|
||
<el-option
|
||
v-for="item in assistantOptions"
|
||
:key="item.id"
|
||
:label="
|
||
item.dept_names ? `${item.name}(${item.dept_names})` : item.name
|
||
"
|
||
:value="Number(item.id)"
|
||
/>
|
||
</el-select>
|
||
<template #footer>
|
||
<el-button @click="assignDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="assignLoading" @click="submitBatchAssign">确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 支付弹窗 -->
|
||
<el-dialog
|
||
v-model="payDialogVisible"
|
||
title="订单支付"
|
||
width="500px"
|
||
>
|
||
<el-form :model="payForm" label-width="100px">
|
||
<el-alert
|
||
v-if="payForm.fubeiPendingAudit"
|
||
type="warning"
|
||
:closable="false"
|
||
class="mb-3"
|
||
title="该单为付呗·待审核,请选择实际到账方式后确认「已支付」"
|
||
/>
|
||
<el-form-item v-if="!payForm.fubeiPendingAudit" label="支付类型" required>
|
||
<el-radio-group v-model="payForm.payType">
|
||
<!-- <el-radio label="normal">正常支付</el-radio> -->
|
||
<el-radio label="supplement">补单支付</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<template v-if="payForm.payType === 'normal'">
|
||
<el-form-item label="订单号">
|
||
<span>{{ payForm.order_no }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="订单金额">
|
||
<span class="text-red-500 font-semibold">¥{{ payForm.amount }}</span>
|
||
</el-form-item>
|
||
</template>
|
||
|
||
<template v-if="!payForm.fubeiPendingAudit && payForm.payType === 'supplement'">
|
||
<el-form-item label="支付订单号" required>
|
||
<el-input
|
||
v-model="payForm.supplement_order_no"
|
||
placeholder="请输入支付订单号"
|
||
/>
|
||
</el-form-item>
|
||
</template>
|
||
<template v-if="payForm.fubeiPendingAudit">
|
||
<el-form-item label="订单号">
|
||
<span>{{ payForm.order_no }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="金额">
|
||
<span class="text-red-500 font-semibold">¥{{ payForm.amount }}</span>
|
||
</el-form-item>
|
||
</template>
|
||
|
||
<el-form-item label="支付方式" required>
|
||
<el-radio-group v-model="payForm.payment_method">
|
||
<el-radio v-if="payForm.fubeiPendingAudit" label="manual">手动确认到账</el-radio>
|
||
<el-radio label="alipay">支付宝</el-radio>
|
||
<el-radio label="wechat">微信</el-radio>
|
||
<el-radio label="wechat_work">企业微信</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="payDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="confirmPay" :loading="payLoading">确认支付</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 创建订单弹窗 -->
|
||
<el-dialog
|
||
v-model="createDialogVisible"
|
||
title="创建订单"
|
||
width="600px"
|
||
@close="resetCreateForm"
|
||
>
|
||
<el-form
|
||
ref="createFormRef"
|
||
:model="createForm"
|
||
:rules="createRules"
|
||
label-width="100px"
|
||
>
|
||
<el-form-item label="创建方式">
|
||
<el-radio-group v-model="createForm.createType">
|
||
<el-radio label="normal">普通订单</el-radio>
|
||
<el-radio label="wechat_work">企业微信对外收款</el-radio>
|
||
<el-radio label="fubei">付呗</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-alert
|
||
v-if="createForm.createType === 'wechat_work'"
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
class="mb-4"
|
||
>
|
||
<template #title>企业微信对外收款</template>
|
||
创建后将生成订单号,请在企业微信发起收款时将该订单号填写为「商户订单号」,收款成功后系统将自动更新订单状态。
|
||
</el-alert>
|
||
<el-alert
|
||
v-if="createForm.createType === 'fubei'"
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
class="mb-4"
|
||
>
|
||
<template #title>付呗</template>
|
||
通过付呗收款的支付单,创建后请按实际对账/审核流程在列表中处理。
|
||
</el-alert>
|
||
<el-form-item v-if="createForm.createType === 'fubei'" label="支付单审核">
|
||
<el-switch
|
||
v-model="createForm.requirePaymentSlipAudit"
|
||
active-text="申请审核"
|
||
inactive-text="不申请"
|
||
/>
|
||
<div class="text-xs text-gray-500 mt-1">
|
||
开启后,新订单的支付状态为「待审核」;未开启则为「待支付」。
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="患者" prop="patient_id">
|
||
<el-select
|
||
v-model="createForm.patient_id"
|
||
placeholder="请选择患者"
|
||
filterable
|
||
remote
|
||
:remote-method="searchPatients"
|
||
:loading="patientLoading"
|
||
>
|
||
<el-option
|
||
v-for="item in patientList"
|
||
:key="item.id"
|
||
:label="`${item.patient_name} (${item.phone})`"
|
||
:value="item.id"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="订单类型" prop="order_type">
|
||
<el-select v-model="createForm.order_type" placeholder="请选择">
|
||
<el-option label="挂号费" :value="1" />
|
||
<el-option label="问诊费" :value="2" />
|
||
<el-option label="药品费用" :value="3" />
|
||
<el-option label="首付费用" :value="4" />
|
||
<el-option label="尾款费用" :value="5" />
|
||
<el-option label="其他费用" :value="6" />
|
||
<el-option label="全部费用" :value="7" />
|
||
<el-option label="驼奶费用" :value="8" />
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="订单金额" prop="amount">
|
||
<el-input-number
|
||
v-model="createForm.amount"
|
||
:min="0"
|
||
:step="0.01"
|
||
:precision="2"
|
||
placeholder="请输入金额"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="备注" prop="remark">
|
||
<el-input
|
||
v-model="createForm.remark"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入备注(可选)"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<template #footer>
|
||
<el-button @click="createDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="submitCreateOrder" :loading="createLoading">
|
||
{{ createForm.createType === 'wechat_work' ? '创建并获取收款订单号' : '创建订单' }}
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
<!-- 同步企业微信收款弹窗 -->
|
||
<el-dialog
|
||
v-model="syncDialogVisible"
|
||
title="同步企业微信对外收款"
|
||
width="420px"
|
||
>
|
||
<p class="text-gray-600 mb-4">拉取员工在企业微信内直接发起的对外收款记录,自动创建订单(未经过后台创建的收款)</p>
|
||
<el-form label-width="90px">
|
||
<el-form-item label="时间范围">
|
||
<el-date-picker
|
||
v-model="syncDateRange"
|
||
type="daterange"
|
||
range-separator="至"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
value-format="YYYY-MM-DD"
|
||
:shortcuts="syncDateShortcuts"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="收款人userid">
|
||
<el-input v-model="syncPayeeUserid" placeholder="可选,如 GaoXingLiang,排查时若返回 try_payee_userids 可逐个尝试" clearable />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="syncDialogVisible = false">取消</el-button>
|
||
<el-button type="info" :loading="syncDebugLoading" @click="handleSyncDebug">
|
||
排查同步为0
|
||
</el-button>
|
||
<el-button type="primary" :loading="syncLoading" @click="submitSyncWechatWork">
|
||
开始同步
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
<!-- 编辑订单弹窗 -->
|
||
<el-dialog
|
||
v-model="editOrderDialogVisible"
|
||
title="编辑订单"
|
||
width="480px"
|
||
>
|
||
<el-form v-if="editOrderForm" ref="editOrderFormRef" :model="editOrderForm" :rules="editOrderRules" label-width="100px">
|
||
<el-form-item label="关联患者" prop="patient_id">
|
||
<el-select
|
||
v-model="editOrderForm.patient_id"
|
||
filterable
|
||
remote
|
||
placeholder="搜索患者姓名/手机号,可留空表示待关联"
|
||
:remote-method="searchPatientsForEdit"
|
||
:loading="editPatientLoading"
|
||
clearable
|
||
class="w-full"
|
||
>
|
||
<el-option
|
||
v-for="item in editPatientList"
|
||
:key="item.id"
|
||
:label="`${item.patient_name || '-'} (${item.phone || '-'})`"
|
||
:value="item.id"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="订单类型" prop="order_type">
|
||
<el-select v-model="editOrderForm.order_type" placeholder="请选择" class="w-full">
|
||
<el-option label="挂号费" :value="1" />
|
||
<el-option label="问诊费" :value="2" />
|
||
<el-option label="药品费用" :value="3" />
|
||
<el-option label="首付费用" :value="4" />
|
||
<el-option label="尾款费用" :value="5" />
|
||
<el-option label="其他费用" :value="6" />
|
||
<el-option label="全部费用" :value="7" />
|
||
<el-option label="驼奶费用" :value="8" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="editOrderDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="editOrderLoading" @click="submitEditOrder">保存</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
<el-dialog v-model="splitDialogVisible" title="拆分订单" width="620px" destroy-on-close @closed="splitSource = null">
|
||
<template v-if="splitSource">
|
||
<p class="text-sm text-gray-600 mb-3">
|
||
原订单号 <span class="font-mono">{{ splitSource.order_no }}</span>,金额
|
||
<span class="text-red-500 font-semibold">¥{{ splitSource.amount }}</span
|
||
>。每笔子单可分别选择<strong>订单类型</strong>;子单继承患者、创建人及企微收款信息;<strong>已支付</strong>子单继承支付方式、支付时间与交易号;原单将软删除;若已关联业务订单,关联会同步到全部子单。
|
||
</p>
|
||
<el-form label-width="88px" @submit.prevent>
|
||
<el-form-item label="拆分管数">
|
||
<el-input-number
|
||
v-model="splitPartCount"
|
||
:min="2"
|
||
:max="20"
|
||
:step="1"
|
||
step-strictly
|
||
@change="redistributeSplitAmounts"
|
||
/>
|
||
<span class="text-xs text-gray-400 ml-2">2~20 笔</span>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div class="space-y-2 mb-3 max-h-[320px] overflow-y-auto pr-1">
|
||
<div v-for="(_amt, idx) in splitAmounts" :key="idx" class="flex items-center gap-2 flex-wrap">
|
||
<span class="text-sm text-gray-500 w-14 shrink-0">第 {{ idx + 1 }} 笔</span>
|
||
<el-select v-model="splitOrderTypes[idx]" placeholder="订单类型" class="w-[136px] shrink-0">
|
||
<el-option label="挂号费" :value="1" />
|
||
<el-option label="问诊费" :value="2" />
|
||
<el-option label="药品费用" :value="3" />
|
||
<el-option label="首付费用" :value="4" />
|
||
<el-option label="尾款费用" :value="5" />
|
||
<el-option label="其他费用" :value="6" />
|
||
<el-option label="全部费用" :value="7" />
|
||
<el-option label="驼奶费用" :value="8" />
|
||
</el-select>
|
||
<el-input-number
|
||
v-model="splitAmounts[idx]"
|
||
:min="0.01"
|
||
:step="0.01"
|
||
:precision="2"
|
||
class="flex-1 min-w-[140px]"
|
||
controls-position="right"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<el-alert
|
||
:type="splitSumMatches ? 'success' : 'warning'"
|
||
:closable="false"
|
||
show-icon
|
||
>
|
||
<template #title>
|
||
子单合计 ¥{{ splitSum.toFixed(2) }},
|
||
{{ splitSumMatches ? '与原价一致,可提交' : '须与原价完全一致方可拆分' }}
|
||
</template>
|
||
</el-alert>
|
||
</template>
|
||
<template #footer>
|
||
<el-button @click="splitDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="splitLoading" :disabled="!splitSumMatches" @click="submitSplitOrder">
|
||
确定拆分
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
<!-- 小程序码弹窗 -->
|
||
<el-dialog
|
||
v-model="qrcodeDialogVisible"
|
||
title="支付小程序码"
|
||
width="400px"
|
||
:close-on-click-modal="false"
|
||
>
|
||
<div class="flex flex-col items-center justify-center py-4">
|
||
<div v-if="qrcodeLoading" class="text-center">
|
||
<el-icon class="is-loading" :size="40">
|
||
<Loading />
|
||
</el-icon>
|
||
<div class="mt-4 text-gray-500">生成中...</div>
|
||
</div>
|
||
<div v-else-if="qrcodeUrl" class="text-center">
|
||
<img :src="qrcodeUrl" alt="小程序二维码" class="w-64 h-64 border border-gray-200 rounded" />
|
||
<div class="mt-4 text-sm text-gray-600">
|
||
<div>订单号:{{ qrcodeOrderNo }}</div>
|
||
<div class="mt-2">请使用微信扫描二维码完成支付</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="text-center text-gray-500">
|
||
生成失败,请重试
|
||
</div>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="qrcodeDialogVisible = false">关闭</el-button>
|
||
<el-button v-if="!qrcodeLoading" type="primary" @click="handleOrderQrcodeRefresh">
|
||
重新生成
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts" name="orderList">
|
||
import { computed } from 'vue'
|
||
import { usePaging } from '@/hooks/usePaging'
|
||
import {
|
||
orderLists,
|
||
orderDetail,
|
||
orderPay,
|
||
orderCancel,
|
||
orderDelete,
|
||
orderRefund,
|
||
orderExport,
|
||
orderCreate,
|
||
orderCreateForWechatWork,
|
||
orderEdit,
|
||
orderTodayRevenue,
|
||
syncWechatWorkBills,
|
||
syncWechatWorkBillsDebug,
|
||
searchPatients as searchPatientsAPI,
|
||
alipayPay,
|
||
wechatPay,
|
||
orderActionLogs,
|
||
orderActionLogStats,
|
||
orderBatchAssignAssistant,
|
||
orderSplit,
|
||
orderSetExempt
|
||
} from '@/api/order'
|
||
import { getAssistants } from '@/api/tcm'
|
||
import { generateOrderQrcode } from '@/api/tcm'
|
||
import { Loading } from '@element-plus/icons-vue'
|
||
import feedback from '@/utils/feedback'
|
||
|
||
const queryParams = reactive({
|
||
order_no: '',
|
||
patient_keyword: '',
|
||
assistant_id: undefined as number | undefined,
|
||
order_type: '',
|
||
status: '',
|
||
patient_association: '' as '' | 'pending' | 'associated',
|
||
create_time_start: '',
|
||
create_time_end: ''
|
||
})
|
||
|
||
const assistantOptions = ref<
|
||
Array<{ id: number; name: string; account?: string; dept_names?: string; dept_ids?: number[] }>
|
||
>([])
|
||
|
||
const orderTableRef = ref<any>(null)
|
||
const selectedOrderRows = ref<any[]>([])
|
||
const selectedOrderIds = computed(() => selectedOrderRows.value.map((r) => r.id as number).filter((id) => id > 0))
|
||
const assignDialogVisible = ref(false)
|
||
const assignLoading = ref(false)
|
||
const assignForm = reactive({
|
||
assistant_id: undefined as number | undefined
|
||
})
|
||
|
||
const patientAssociationTab = ref<'' | 'pending' | 'associated'>('')
|
||
const todayRevenue = ref({ amount: '0.00', count: 0 })
|
||
|
||
const { pager, getLists, resetPage, resetParams } = usePaging({
|
||
fetchFun: orderLists,
|
||
params: queryParams
|
||
})
|
||
|
||
const handleOrderSelectionChange = (rows: any[]) => {
|
||
selectedOrderRows.value = rows || []
|
||
}
|
||
|
||
const openAssignDialog = () => {
|
||
if (!selectedOrderIds.value.length) {
|
||
feedback.msgWarning('请先勾选要指派的支付单')
|
||
return
|
||
}
|
||
assignForm.assistant_id = undefined
|
||
assignDialogVisible.value = true
|
||
}
|
||
|
||
const submitBatchAssign = async () => {
|
||
if (assignForm.assistant_id == null) {
|
||
feedback.msgWarning('请选择医助')
|
||
return
|
||
}
|
||
assignLoading.value = true
|
||
try {
|
||
await orderBatchAssignAssistant({
|
||
order_ids: [...selectedOrderIds.value],
|
||
assistant_id: assignForm.assistant_id
|
||
})
|
||
feedback.msgSuccess('指派成功')
|
||
assignDialogVisible.value = false
|
||
orderTableRef.value?.clearSelection()
|
||
getLists()
|
||
fetchTodayRevenue()
|
||
} catch (e: any) {
|
||
if (e?.msg) feedback.msgError(e.msg)
|
||
} finally {
|
||
assignLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 搜索
|
||
const handleSearch = () => {
|
||
resetPage()
|
||
}
|
||
|
||
// Tab 切换
|
||
const handleTabChange = (name: string | number) => {
|
||
queryParams.patient_association = (name as '' | 'pending' | 'associated') || ''
|
||
resetPage()
|
||
}
|
||
|
||
// 获取今日收益
|
||
const fetchTodayRevenue = async () => {
|
||
try {
|
||
const res: any = await orderTodayRevenue()
|
||
todayRevenue.value = res?.data || res || { amount: '0.00', count: 0 }
|
||
} catch (e) {
|
||
todayRevenue.value = { amount: '0.00', count: 0 }
|
||
}
|
||
}
|
||
|
||
// 重置
|
||
const handleReset = () => {
|
||
patientAssociationTab.value = ''
|
||
resetParams()
|
||
}
|
||
|
||
const detailVisible = ref(false)
|
||
const detailData = ref<any>(null)
|
||
const currentOrderIdForLog = ref(0)
|
||
const actionLogLoading = ref(false)
|
||
const actionLogList = ref<any[]>([])
|
||
const actionLogTotal = ref(0)
|
||
const actionLogPage = ref(1)
|
||
const actionLogPageSize = ref(10)
|
||
|
||
const statsDialogVisible = ref(false)
|
||
const statsLoading = ref(false)
|
||
const statsList = ref<any[]>([])
|
||
const statsDateRange = ref<[string, string] | null>(null)
|
||
const statsRangeText = ref('')
|
||
|
||
const loadOrderActionLogs = async () => {
|
||
if (!currentOrderIdForLog.value) {
|
||
return
|
||
}
|
||
actionLogLoading.value = true
|
||
try {
|
||
const res: any = await orderActionLogs({
|
||
order_id: currentOrderIdForLog.value,
|
||
page_no: actionLogPage.value,
|
||
page_size: actionLogPageSize.value
|
||
})
|
||
actionLogList.value = res?.lists || []
|
||
actionLogTotal.value = res?.count ?? 0
|
||
} catch {
|
||
actionLogList.value = []
|
||
actionLogTotal.value = 0
|
||
} finally {
|
||
actionLogLoading.value = false
|
||
}
|
||
}
|
||
|
||
const onActionLogSizeChange = () => {
|
||
actionLogPage.value = 1
|
||
loadOrderActionLogs()
|
||
}
|
||
|
||
const onStatsDialogOpen = () => {
|
||
statsDateRange.value = null
|
||
fetchActionLogStats()
|
||
}
|
||
|
||
const fetchActionLogStats = async () => {
|
||
statsLoading.value = true
|
||
try {
|
||
const params: { start_time?: string; end_time?: string } = {}
|
||
if (statsDateRange.value && statsDateRange.value.length === 2) {
|
||
params.start_time = statsDateRange.value[0]
|
||
params.end_time = statsDateRange.value[1]
|
||
}
|
||
const res: any = await orderActionLogStats(Object.keys(params).length ? params : undefined)
|
||
statsList.value = res?.list || []
|
||
if (res?.start_time && res?.end_time) {
|
||
statsRangeText.value = `${res.start_time} ~ ${res.end_time}`
|
||
} else {
|
||
statsRangeText.value = ''
|
||
}
|
||
} catch {
|
||
statsList.value = []
|
||
statsRangeText.value = ''
|
||
} finally {
|
||
statsLoading.value = false
|
||
}
|
||
}
|
||
const payDialogVisible = ref(false)
|
||
const payLoading = ref(false)
|
||
const payForm = ref({
|
||
order_id: 0,
|
||
order_no: '',
|
||
amount: 0,
|
||
/** 付呗+待审核:简化弹窗,确认后标已付 */
|
||
fubeiPendingAudit: false,
|
||
payment_method: 'alipay' as 'alipay' | 'wechat' | 'wechat_work' | 'manual',
|
||
payType: 'supplement' as 'supplement',
|
||
supplement_order_no: ''
|
||
})
|
||
|
||
// 创建订单相关
|
||
const createDialogVisible = ref(false)
|
||
const createFormRef = ref()
|
||
const createLoading = ref(false)
|
||
const patientLoading = ref(false)
|
||
const patientList = ref<any[]>([])
|
||
|
||
const createForm = reactive({
|
||
createType: 'normal' as 'normal' | 'wechat_work' | 'fubei',
|
||
/** 仅「付呗」:开启后订单为待审核(5) */
|
||
requirePaymentSlipAudit: false,
|
||
patient_id: '',
|
||
order_type: '',
|
||
amount: 0,
|
||
remark: ''
|
||
})
|
||
|
||
const createRules = {
|
||
patient_id: [{ required: true, message: '请选择患者', trigger: 'change' }],
|
||
order_type: [{ required: true, message: '请选择订单类型', trigger: 'change' }],
|
||
amount: [{ required: true, message: '请输入订单金额', trigger: 'blur' }]
|
||
}
|
||
|
||
// 编辑订单相关
|
||
const editOrderDialogVisible = ref(false)
|
||
const editOrderFormRef = ref()
|
||
const editOrderLoading = ref(false)
|
||
const editPatientLoading = ref(false)
|
||
const editPatientList = ref<any[]>([])
|
||
const editOrderForm = ref<{ id: number; patient_id: number | null; order_type: number } | null>(null)
|
||
const editOrderRules = {
|
||
order_type: [{ required: true, message: '请选择订单类型', trigger: 'change' }]
|
||
}
|
||
|
||
// 搜索患者
|
||
const searchPatients = async (query: string) => {
|
||
if (!query) {
|
||
patientList.value = []
|
||
return
|
||
}
|
||
try {
|
||
patientLoading.value = true
|
||
const res = await searchPatientsAPI({
|
||
keyword: query,
|
||
page_no: 1,
|
||
page_size: 10
|
||
})
|
||
patientList.value = res?.lists || []
|
||
} catch (error) {
|
||
console.error('搜索患者失败:', error)
|
||
} finally {
|
||
patientLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 重置创建表单
|
||
const resetCreateForm = () => {
|
||
createForm.createType = 'normal'
|
||
createForm.requirePaymentSlipAudit = false
|
||
createForm.patient_id = ''
|
||
createForm.order_type = ''
|
||
createForm.amount = 0
|
||
createForm.remark = ''
|
||
createFormRef.value?.clearValidate()
|
||
}
|
||
|
||
// 打开创建订单弹窗
|
||
const handleCreate = () => {
|
||
resetCreateForm()
|
||
createDialogVisible.value = true
|
||
}
|
||
|
||
// 同步企业微信收款
|
||
const syncDialogVisible = ref(false)
|
||
const syncLoading = ref(false)
|
||
const syncDebugLoading = ref(false)
|
||
const syncPayeeUserid = ref('')
|
||
const syncDateRange = ref<[string, string]>([
|
||
new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10),
|
||
new Date().toISOString().slice(0, 10)
|
||
])
|
||
const syncDateShortcuts = [
|
||
{ text: '最近7天', value: () => {
|
||
const end = new Date()
|
||
const start = new Date()
|
||
start.setTime(start.getTime() - 7 * 86400000)
|
||
return [start, end]
|
||
}},
|
||
{ text: '最近30天', value: () => {
|
||
const end = new Date()
|
||
const start = new Date()
|
||
start.setTime(start.getTime() - 30 * 86400000)
|
||
return [start, end]
|
||
}},
|
||
{ text: '今天', value: () => {
|
||
const d = new Date()
|
||
return [d, d]
|
||
}}
|
||
]
|
||
|
||
const handleSyncWechatWork = () => {
|
||
syncDateRange.value = [
|
||
new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10),
|
||
new Date().toISOString().slice(0, 10)
|
||
]
|
||
syncDialogVisible.value = true
|
||
}
|
||
|
||
const handleSyncDebug = async () => {
|
||
const range = syncDateRange.value
|
||
if (!range || range.length !== 2) {
|
||
feedback.msgError('请先选择时间范围')
|
||
return
|
||
}
|
||
try {
|
||
syncDebugLoading.value = true
|
||
const res: any = await syncWechatWorkBillsDebug({
|
||
begin_time: range[0],
|
||
end_time: range[1],
|
||
payee_userid: syncPayeeUserid.value || undefined
|
||
})
|
||
const d = res?.data || res
|
||
let msg = `配置:${d.config_ok ? '✓' : '✗'} | token:${d.access_token} | errcode:${d.api_errcode} | errmsg:${d.api_errmsg || '-'} | 账单数:${d.bill_count}`
|
||
if (d.try_payee_userids?.length) {
|
||
msg += `。可尝试收款人: ${d.try_payee_userids.join(', ')}`
|
||
}
|
||
if (d.api_errcode !== 0 && d.api_errcode !== -999) {
|
||
msg += '。提示:需使用「对外收款」应用的Secret(管理后台-应用管理-对外收款-API)'
|
||
}
|
||
feedback.msgSuccess(msg)
|
||
console.log('企业微信同步调试:', d)
|
||
} catch (e: any) {
|
||
feedback.msgError(e?.message || '排查失败')
|
||
} finally {
|
||
syncDebugLoading.value = false
|
||
}
|
||
}
|
||
|
||
const submitSyncWechatWork = async () => {
|
||
const range = syncDateRange.value
|
||
if (!range || range.length !== 2) {
|
||
feedback.msgError('请选择时间范围')
|
||
return
|
||
}
|
||
try {
|
||
syncLoading.value = true
|
||
const res: any = await syncWechatWorkBills({
|
||
begin_time: range[0],
|
||
end_time: range[1]
|
||
})
|
||
const data = res?.data || res
|
||
syncDialogVisible.value = false
|
||
getLists()
|
||
const msg = `同步完成:新增 ${data.created || 0} 笔,更新 ${data.updated || 0} 笔,跳过 ${data.skipped || 0} 笔` +
|
||
(data.errors?.length ? `;错误:${data.errors.join(';')}` : '')
|
||
feedback.msgSuccess(msg)
|
||
fetchTodayRevenue()
|
||
} catch (error: any) {
|
||
feedback.msgError(error?.message || '同步失败')
|
||
} finally {
|
||
syncLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 提交创建订单
|
||
const submitCreateOrder = async () => {
|
||
try {
|
||
await createFormRef.value?.validate()
|
||
|
||
createLoading.value = true
|
||
|
||
if (createForm.createType === 'wechat_work') {
|
||
const params = {
|
||
patient_id: createForm.patient_id,
|
||
order_type: createForm.order_type,
|
||
amount: createForm.amount,
|
||
remark: createForm.remark
|
||
}
|
||
const res: any = await orderCreateForWechatWork(params)
|
||
const orderNo = res?.order_no ?? res?.data?.order_no
|
||
createDialogVisible.value = false
|
||
getLists()
|
||
feedback.msgSuccess(orderNo ? `订单创建成功,请在企业微信收款时使用订单号:${orderNo}` : '订单创建成功')
|
||
} else {
|
||
const params: Record<string, unknown> = {
|
||
patient_id: createForm.patient_id,
|
||
order_type: createForm.order_type,
|
||
amount: createForm.amount,
|
||
remark: createForm.remark
|
||
}
|
||
if (createForm.createType === 'fubei') {
|
||
params.payment_channel = 'fubei'
|
||
params.require_payment_slip_audit = createForm.requirePaymentSlipAudit ? 1 : 0
|
||
} else {
|
||
params.payment_channel = 'normal'
|
||
params.require_payment_slip_audit = 0
|
||
}
|
||
await orderCreate(params)
|
||
const tip =
|
||
createForm.createType === 'fubei' && createForm.requirePaymentSlipAudit
|
||
? '订单已创建,支付状态为「待审核」'
|
||
: '订单创建成功'
|
||
feedback.msgSuccess(tip)
|
||
createDialogVisible.value = false
|
||
getLists()
|
||
}
|
||
} catch (error) {
|
||
console.error('创建订单失败:', error)
|
||
} finally {
|
||
createLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 获取订单类型文本
|
||
const getOrderTypeText = (type: number) => {
|
||
const typeMap: Record<number, string> = {
|
||
1: '挂号费',
|
||
2: '问诊费',
|
||
3: '药品费用',
|
||
4: '首付费用',
|
||
5: '尾款费用',
|
||
6: '其他费用',
|
||
7: '全部费用',
|
||
8: '驼奶费用',
|
||
}
|
||
return typeMap[type] || '未知'
|
||
}
|
||
|
||
// 获取订单类型标签类型
|
||
const getOrderTypeTag = (type: number): 'success' | 'info' | 'warning' => {
|
||
const tagMap: Record<number, 'success' | 'info' | 'warning'> = {
|
||
1: 'success',
|
||
2: 'info',
|
||
3: 'warning'
|
||
}
|
||
return tagMap[type] || 'info'
|
||
}
|
||
|
||
// 获取订单状态文本
|
||
const getStatusText = (status: number) => {
|
||
const statusMap: Record<number, string> = {
|
||
1: '待支付',
|
||
2: '已支付',
|
||
3: '已取消',
|
||
4: '已退款',
|
||
5: '待审核'
|
||
}
|
||
return statusMap[status] || '未知'
|
||
}
|
||
|
||
// 获取订单状态标签类型
|
||
const getStatusTag = (status: number): 'warning' | 'success' | 'info' | 'danger' => {
|
||
const tagMap: Record<number, 'warning' | 'success' | 'info' | 'danger'> = {
|
||
1: 'warning',
|
||
2: 'success',
|
||
3: 'info',
|
||
4: 'danger',
|
||
5: 'warning'
|
||
}
|
||
return tagMap[status] || 'info'
|
||
}
|
||
|
||
// 获取支付方式文本
|
||
const getPaymentMethodText = (method: string | null) => {
|
||
if (!method) return '-'
|
||
const methodMap: Record<string, string> = {
|
||
alipay: '支付宝',
|
||
wechat: '微信',
|
||
wechat_work: '企业微信',
|
||
fubei: '付呗',
|
||
manual: '手动确认'
|
||
}
|
||
return methodMap[method] || '未知'
|
||
}
|
||
|
||
// 编辑订单
|
||
const handleEditOrder = (row: any) => {
|
||
editOrderForm.value = {
|
||
id: row.id,
|
||
patient_id: row.patient_id || null,
|
||
order_type: row.order_type
|
||
}
|
||
editPatientList.value = row.patient ? [row.patient] : []
|
||
editOrderDialogVisible.value = true
|
||
}
|
||
|
||
const searchPatientsForEdit = async (query: string) => {
|
||
if (!query) {
|
||
editPatientList.value = []
|
||
return
|
||
}
|
||
try {
|
||
editPatientLoading.value = true
|
||
const res = await searchPatientsAPI({ keyword: query, page_no: 1, page_size: 10 })
|
||
editPatientList.value = res?.lists || []
|
||
} catch (e) {
|
||
editPatientList.value = []
|
||
} finally {
|
||
editPatientLoading.value = false
|
||
}
|
||
}
|
||
|
||
const submitEditOrder = async () => {
|
||
if (!editOrderForm.value) return
|
||
try {
|
||
await editOrderFormRef.value?.validate()
|
||
editOrderLoading.value = true
|
||
await orderEdit({
|
||
id: editOrderForm.value.id,
|
||
patient_id: editOrderForm.value.patient_id ?? 0,
|
||
order_type: editOrderForm.value.order_type
|
||
})
|
||
feedback.msgSuccess('保存成功')
|
||
editOrderDialogVisible.value = false
|
||
getLists()
|
||
fetchTodayRevenue()
|
||
} catch (e: any) {
|
||
if (e?.message) feedback.msgError(e.message)
|
||
} finally {
|
||
editOrderLoading.value = false
|
||
}
|
||
}
|
||
|
||
/** 拆分订单(待支付 / 已支付 / 付呗待审核) */
|
||
const splitDialogVisible = ref(false)
|
||
const splitSource = ref<any>(null)
|
||
const splitPartCount = ref(2)
|
||
const splitAmounts = ref<number[]>([])
|
||
const splitOrderTypes = ref<number[]>([])
|
||
const splitLoading = ref(false)
|
||
|
||
const redistributeSplitAmounts = () => {
|
||
if (!splitSource.value) return
|
||
const total = Number(splitSource.value.amount)
|
||
const n = Math.max(2, Math.min(20, Math.floor(Number(splitPartCount.value) || 2)))
|
||
splitPartCount.value = n
|
||
const cents = Math.round(total * 100)
|
||
const base = Math.floor(cents / n)
|
||
const rem = cents % n
|
||
const next: number[] = []
|
||
for (let i = 0; i < n; i++) {
|
||
const c = base + (i < rem ? 1 : 0)
|
||
next.push(c / 100)
|
||
}
|
||
const prevTypes = [...splitOrderTypes.value]
|
||
const defaultType = Number(splitSource.value.order_type ?? 6)
|
||
splitOrderTypes.value = next.map((_, i) => (i < prevTypes.length ? prevTypes[i]! : defaultType))
|
||
splitAmounts.value = next
|
||
}
|
||
|
||
const openSplitOrder = (row: any) => {
|
||
splitSource.value = row
|
||
splitPartCount.value = 2
|
||
redistributeSplitAmounts()
|
||
splitDialogVisible.value = true
|
||
}
|
||
|
||
const splitSum = computed(() => splitAmounts.value.reduce((s, x) => s + Number(x || 0), 0))
|
||
|
||
const splitSumMatches = computed(() => {
|
||
if (!splitSource.value) return false
|
||
const orig = Number(splitSource.value.amount)
|
||
return Math.abs(splitSum.value - orig) < 0.0001
|
||
})
|
||
|
||
const submitSplitOrder = async () => {
|
||
if (!splitSource.value || !splitSumMatches.value) return
|
||
splitLoading.value = true
|
||
try {
|
||
await orderSplit({
|
||
id: splitSource.value.id,
|
||
amounts: splitAmounts.value.map((x) => Number(x)),
|
||
order_types: splitOrderTypes.value.map((x) => Number(x))
|
||
})
|
||
feedback.msgSuccess('拆分成功')
|
||
splitDialogVisible.value = false
|
||
getLists()
|
||
fetchTodayRevenue()
|
||
} catch (e: any) {
|
||
if (e?.msg) feedback.msgError(e.msg)
|
||
} finally {
|
||
splitLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 查看详情
|
||
const handleDetail = async (row: any) => {
|
||
try {
|
||
const res = await orderDetail({ id: row.id })
|
||
detailData.value = res
|
||
currentOrderIdForLog.value = row.id
|
||
actionLogPage.value = 1
|
||
detailVisible.value = true
|
||
loadOrderActionLogs()
|
||
} catch (error) {
|
||
console.error('获取详情失败:', error)
|
||
}
|
||
}
|
||
|
||
// 支付订单
|
||
const handlePay = (row: any) => {
|
||
const fubeiPending = row.status === 5 && row.payment_method === 'fubei'
|
||
payForm.value = {
|
||
order_id: row.id,
|
||
order_no: row.order_no,
|
||
amount: row.amount,
|
||
fubeiPendingAudit: fubeiPending,
|
||
payment_method: fubeiPending ? 'manual' : 'alipay',
|
||
payType: 'supplement',
|
||
supplement_order_no: ''
|
||
}
|
||
payDialogVisible.value = true
|
||
}
|
||
|
||
// 确认支付
|
||
const confirmPay = async () => {
|
||
try {
|
||
// 付呗待审核:直接标记已支付
|
||
if (payForm.value.fubeiPendingAudit) {
|
||
payLoading.value = true
|
||
await orderPay({
|
||
id: payForm.value.order_id,
|
||
payment_method: payForm.value.payment_method
|
||
})
|
||
feedback.msgSuccess('已更新为已支付')
|
||
payDialogVisible.value = false
|
||
getLists()
|
||
fetchTodayRevenue()
|
||
return
|
||
}
|
||
|
||
// 验证补单支付的订单号
|
||
if (payForm.value.payType === 'supplement' && !payForm.value.supplement_order_no) {
|
||
feedback.msgWarning('请输入支付订单号')
|
||
return
|
||
}
|
||
|
||
payLoading.value = true
|
||
|
||
let payParams: any = {}
|
||
|
||
if (payForm.value.payType === 'normal') {
|
||
// 正常支付
|
||
payParams = {
|
||
id: payForm.value.order_id,
|
||
order_no: payForm.value.order_no,
|
||
amount: payForm.value.amount,
|
||
payment_method: payForm.value.payment_method
|
||
}
|
||
} else {
|
||
// 补单支付:直接填订单号,不需要校验
|
||
payParams = {
|
||
order_no: payForm.value.supplement_order_no,
|
||
payment_method: payForm.value.payment_method,
|
||
is_supplement: 1
|
||
}
|
||
}
|
||
|
||
// 企业微信补单:直接调用 orderPay 更新订单状态
|
||
if (payForm.value.payment_method === 'wechat_work') {
|
||
await orderPay(payParams)
|
||
feedback.msgSuccess('补单成功')
|
||
} else {
|
||
// 调用对应的支付网关
|
||
let payResult: any
|
||
if (payForm.value.payment_method === 'alipay') {
|
||
payResult = await alipayPay(payParams)
|
||
} else if (payForm.value.payment_method === 'wechat') {
|
||
payResult = await wechatPay(payParams)
|
||
}
|
||
|
||
// 支付网关返回支付链接或二维码
|
||
if (payResult && payResult.pay_url) {
|
||
// 如果是支付宝,跳转到支付页面
|
||
if (payForm.value.payment_method === 'alipay') {
|
||
window.location.href = payResult.pay_url
|
||
} else if (payForm.value.payment_method === 'wechat') {
|
||
// 如果是微信,显示二维码或其他处理
|
||
feedback.msgSuccess('请扫描二维码进行支付')
|
||
}
|
||
}
|
||
}
|
||
|
||
payDialogVisible.value = false
|
||
// 延迟刷新列表,等待支付完成
|
||
setTimeout(() => {
|
||
getLists()
|
||
}, 2000)
|
||
} catch (error) {
|
||
console.error('支付失败:', error)
|
||
feedback.msgError('支付失败,请重试')
|
||
} finally {
|
||
payLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 取消订单
|
||
const handleCancel = async (row: any) => {
|
||
try {
|
||
await feedback.confirm('确定要取消该订单吗?')
|
||
await orderCancel({ id: row.id })
|
||
feedback.msgSuccess('取消成功')
|
||
getLists()
|
||
} catch (error) {
|
||
// 用户取消操作
|
||
}
|
||
}
|
||
|
||
// 退款订单
|
||
const handleRefund = async (row: any) => {
|
||
try {
|
||
await feedback.confirm('确定要退款该订单吗?')
|
||
await orderRefund({ id: row.id })
|
||
feedback.msgSuccess('退款成功')
|
||
getLists()
|
||
} catch (error) {
|
||
// 用户取消操作
|
||
}
|
||
}
|
||
|
||
// 删除订单
|
||
const handleDelete = async (row: any) => {
|
||
try {
|
||
await feedback.confirm('确定要删除该订单吗?此操作不可恢复')
|
||
await orderDelete({ id: row.id })
|
||
feedback.msgSuccess('删除成功')
|
||
getLists()
|
||
} catch (error) {
|
||
// 用户取消操作
|
||
}
|
||
}
|
||
|
||
const handleToggleExempt = async (row: any) => {
|
||
const newVal: 0 | 1 = row.is_exempt === 1 ? 0 : 1
|
||
const actionText = newVal === 1 ? '设为豁免' : '取消豁免'
|
||
try {
|
||
await feedback.confirm(`确定要${actionText}该支付单吗?豁免后关联此单的业务订单将跳过定金门槛校验。`)
|
||
await orderSetExempt({ id: row.id, is_exempt: newVal })
|
||
feedback.msgSuccess(`${actionText}成功`)
|
||
getLists()
|
||
} catch (error) {
|
||
// 用户取消操作
|
||
}
|
||
}
|
||
|
||
// 小程序码相关
|
||
const qrcodeDialogVisible = ref(false)
|
||
const qrcodeLoading = ref(false)
|
||
const qrcodeUrl = ref('')
|
||
const qrcodeOrderNo = ref('')
|
||
|
||
const handleOrderQrcode = async (row: any) => {
|
||
if (!row.order_no) {
|
||
feedback.msgWarning('订单号不存在')
|
||
return
|
||
}
|
||
qrcodeOrderNo.value = row.order_no
|
||
qrcodeDialogVisible.value = true
|
||
qrcodeLoading.value = true
|
||
qrcodeUrl.value = ''
|
||
try {
|
||
const res = await generateOrderQrcode({ order_no: row.order_no })
|
||
if (res?.qrcode_url) {
|
||
qrcodeUrl.value = res.qrcode_url
|
||
} else {
|
||
feedback.msgError('二维码生成失败')
|
||
}
|
||
} catch (e: any) {
|
||
feedback.msgError(e?.msg || '生成二维码失败')
|
||
} finally {
|
||
qrcodeLoading.value = false
|
||
}
|
||
}
|
||
|
||
const handleOrderQrcodeRefresh = () => {
|
||
if (qrcodeOrderNo.value) {
|
||
handleOrderQrcode({ order_no: qrcodeOrderNo.value })
|
||
}
|
||
}
|
||
|
||
const loadAssistantOptions = async () => {
|
||
try {
|
||
const list = await getAssistants()
|
||
assistantOptions.value = Array.isArray(list) ? list : []
|
||
} catch {
|
||
assistantOptions.value = []
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
loadAssistantOptions()
|
||
getLists()
|
||
fetchTodayRevenue()
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.order-list {
|
||
padding: 20px;
|
||
}
|
||
</style>
|