Files
zyt/admin/src/views/order/index.vue
T
2026-03-14 15:39:34 +08:00

731 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 订单列表 -->
<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.order_type" placeholder="请选择" clearable>
<el-option label="挂号费" :value="1" />
<el-option label="问诊费" :value="2" />
<el-option label="药品费用" :value="3" />
</el-select>
</el-form-item>
<el-form-item class="w-[280px]" label="订单状态">
<el-select v-model="queryParams.status" placeholder="请选择" clearable>
<el-option label="待支付" :value="1" />
<el-option label="已支付" :value="2" />
<el-option label="已取消" :value="3" />
<el-option label="已退款" :value="4" />
</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>
<export-data
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-form-item>
</el-form>
</el-card>
<!-- 数据表格 -->
<el-card class="!border-none mt-4" shadow="never">
<el-table
v-loading="pager.loading"
:data="pager.lists"
size="large"
>
<el-table-column label="订单号" prop="order_no" min-width="150" />
<el-table-column label="患者信息" min-width="150">
<template #default="{ row }">
<div>{{ row.patient.patient_name }}</div>
<div class="text-xs text-gray-400">{{ row.patient.phone }}</div>
</template>
</el-table-column>
<el-table-column label="创建人" prop="creator.name" min-width="120" />
<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="100">
<template #default="{ row }">
<el-tag :type="getStatusTag(row.status)">
{{ getStatusText(row.status) }}
</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="280" fixed="right">
<template #default="{ row }">
<el-button
v-perms="['order.order/detail']"
type="primary"
link
@click="handleDetail(row)"
>
详情
</el-button>
<el-button
type="primary"
link
@click="handleOrderQrcode(row)"
>
小程序码
</el-button>
<el-button
v-if="row.status === 1"
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"
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>
</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="700px"
>
<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="订单类型">
<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-dialog>
<!-- 支付弹窗 -->
<el-dialog
v-model="payDialogVisible"
title="订单支付"
width="500px"
>
<el-form :model="payForm" label-width="100px">
<el-form-item 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.payType === 'supplement'">
<el-form-item label="支付订单号" required>
<el-input
v-model="payForm.supplement_order_no"
placeholder="请输入支付订单号"
/>
</el-form-item>
</template>
<el-form-item label="支付方式" required>
<el-radio-group v-model="payForm.payment_method">
<el-radio label="alipay">支付宝</el-radio>
<el-radio label="wechat">微信</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="患者" 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-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">
创建订单
</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 { usePaging } from '@/hooks/usePaging'
import { orderLists, orderDetail, orderPay, orderCancel, orderDelete, orderRefund, orderExport, orderCreate, searchPatients as searchPatientsAPI, alipayPay, wechatPay } from '@/api/order'
import { generateOrderQrcode } from '@/api/tcm'
import { Loading } from '@element-plus/icons-vue'
import feedback from '@/utils/feedback'
const queryParams = reactive({
order_no: '',
patient_keyword: '',
order_type: '',
status: '',
create_time_start: '',
create_time_end: ''
})
const { pager, getLists, resetPage, resetParams } = usePaging({
fetchFun: orderLists,
params: queryParams
})
// 搜索
const handleSearch = () => {
resetPage()
}
// 重置
const handleReset = () => {
resetParams()
}
const detailVisible = ref(false)
const detailData = ref<any>(null)
const payDialogVisible = ref(false)
const payLoading = ref(false)
const payForm = ref({
order_id: 0,
order_no: '',
amount: 0,
payment_method: 'alipay',
payType: '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({
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 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.patient_id = ''
createForm.order_type = ''
createForm.amount = 0
createForm.remark = ''
createFormRef.value?.clearValidate()
}
// 打开创建订单弹窗
const handleCreate = () => {
resetCreateForm()
createDialogVisible.value = true
}
// 提交创建订单
const submitCreateOrder = async () => {
try {
await createFormRef.value?.validate()
createLoading.value = true
const params = {
patient_id: createForm.patient_id,
order_type: createForm.order_type,
amount: createForm.amount,
remark: createForm.remark
}
await orderCreate(params)
feedback.msgSuccess('订单创建成功')
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:'全部费用',
}
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: '已退款'
}
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'
}
return tagMap[status] || 'info'
}
// 获取支付方式文本
const getPaymentMethodText = (method: string | null) => {
if (!method) return '-'
const methodMap: Record<string, string> = {
alipay: '支付宝',
wechat: '微信'
}
return methodMap[method] || '未知'
}
// 查看详情
const handleDetail = async (row: any) => {
try {
const res = await orderDetail({ id: row.id })
detailData.value = res
detailVisible.value = true
} catch (error) {
console.error('获取详情失败:', error)
}
}
// 支付订单
const handlePay = (row: any) => {
payForm.value = {
order_id: row.id,
order_no: row.order_no,
amount: row.amount,
payment_method: 'alipay',
payType: 'supplement',
supplement_order_no: ''
}
payDialogVisible.value = true
}
// 确认支付
const confirmPay = async () => {
try {
// 验证补单支付的订单号
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
}
}
// 调用对应的支付网关
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 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 })
}
}
onMounted(() => {
getLists()
})
</script>
<style scoped lang="scss">
.order-list {
padding: 20px;
}
</style>