148 lines
6.1 KiB
Vue
148 lines
6.1 KiB
Vue
<template>
|
||
<el-dialog v-model="visible" :title="title" width="520px" draggable :close-on-click-modal="false">
|
||
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px" v-loading="loading">
|
||
<el-form-item label="订单号">
|
||
<el-input :model-value="orderLabel" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="凭证类型">
|
||
<el-tag :type="options.printType === '2' ? 'warning' : 'primary'">
|
||
{{ options.printType === "2" ? "兑换券" : "发票" }}
|
||
</el-tag>
|
||
</el-form-item>
|
||
<el-form-item v-if="showSourceSelection" label="来源终端" prop="sourceHardId">
|
||
<el-select v-model="form.sourceHardId" class="w-full" filterable placeholder="请选择已绑定尔印打印机的业务终端">
|
||
<el-option v-for="source in sources" :key="source.id" :label="source.name" :value="String(source.id)" />
|
||
</el-select>
|
||
<div v-if="sources.length === 0" class="mt-1 text-xs text-[var(--el-color-danger)]">
|
||
该商户没有已绑定尔印打印机的业务终端,请先在设备管理完成绑定。
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item v-if="requiresReason" label="重打原因" prop="reprintReason">
|
||
<el-input v-model="form.reprintReason" type="textarea" :rows="3" maxlength="200" show-word-limit
|
||
placeholder="请填写本次人工重打原因" />
|
||
</el-form-item>
|
||
<el-alert v-if="options.action === 'retry'" type="info" :closable="false" show-icon
|
||
title="重试原任务不会增加打印序号;终端只处理新的重试版本。" />
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="visible = false">取消</el-button>
|
||
<el-button type="primary" :loading="submitting" :disabled="sourceUnavailable" @click="submit">
|
||
提交打印任务
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<script setup lang="ts" name="HardPrintTaskDialog">
|
||
import * as hardInvoiceApi from "/@/api/hard/hard-invoice"
|
||
import * as printerApi from "/@/api/hard/hard-printer"
|
||
import { useMessage, useMessageBox } from "/@/hooks/message"
|
||
|
||
type PrintAction = "create" | "retry" | "reprint"
|
||
|
||
interface PrintDialogOptions {
|
||
action: PrintAction
|
||
printType: "1" | "2"
|
||
orderId: string | number
|
||
merchantId?: string | number
|
||
taskId?: string | number
|
||
sourceHardId?: string | number | null
|
||
orderNo?: string
|
||
printNo?: string | number | null
|
||
}
|
||
|
||
const emit = defineEmits(["submitted"])
|
||
const visible = ref(false)
|
||
const loading = ref(false)
|
||
const submitting = ref(false)
|
||
const formRef = ref()
|
||
const sources = ref<any[]>([])
|
||
const options = reactive<PrintDialogOptions>({ action: "create", printType: "1", orderId: "" })
|
||
/** 来源设备使用字符串保存,避免选择或自动回填时破坏后端 Long 主键精度。 */
|
||
const form = reactive<{ sourceHardId: printerApi.HardwareId | null; reprintReason: string }>({
|
||
sourceHardId: null,
|
||
reprintReason: "",
|
||
})
|
||
|
||
/** 已打印任务再次出纸必须记录原因;失败任务重试不改变打印次数。 */
|
||
const requiresReason = computed(() => options.action === "reprint" || Boolean(options.printNo && options.action === "create"))
|
||
const showSourceSelection = computed(() => options.action !== "reprint" && !(options.action === "retry" && options.sourceHardId))
|
||
const sourceUnavailable = computed(() => showSourceSelection.value && !loading.value && sources.value.length === 0)
|
||
const orderLabel = computed(() => options.orderNo || String(options.orderId || "--"))
|
||
const title = computed(() => {
|
||
if (options.action === "retry") return "重试失败打印任务"
|
||
if (options.action === "reprint") return `重打${options.printType === "2" ? "兑换券" : "发票"}`
|
||
return `${requiresReason.value ? "再次打印" : "打印"}${options.printType === "2" ? "兑换券" : "发票"}`
|
||
})
|
||
const rules = computed(() => ({
|
||
sourceHardId: showSourceSelection.value ? [{ required: true, message: "请选择来源业务终端", trigger: "change" }] : [],
|
||
reprintReason: requiresReason.value ? [{ required: true, message: "请填写重打原因", trigger: "blur" }] : [],
|
||
}))
|
||
|
||
/** 打开任务弹窗,并根据商户的有效绑定自动选择唯一来源终端。 */
|
||
const open = async (input: PrintDialogOptions) => {
|
||
Object.assign(options, input)
|
||
form.sourceHardId = null
|
||
form.reprintReason = ""
|
||
sources.value = []
|
||
visible.value = true
|
||
await nextTick()
|
||
formRef.value?.clearValidate?.()
|
||
if (!showSourceSelection.value) return
|
||
if (!input.merchantId) {
|
||
useMessage().error("订单缺少商户信息,无法选择打印终端")
|
||
return
|
||
}
|
||
loading.value = true
|
||
try {
|
||
const response: any = await printerApi.listBoundSources(String(input.merchantId))
|
||
sources.value = response.data || []
|
||
const preferred = sources.value.find((source) => input.sourceHardId != null
|
||
&& String(source.id) === String(input.sourceHardId))
|
||
if (preferred) form.sourceHardId = String(preferred.id)
|
||
else if (sources.value.length === 1) form.sourceHardId = String(sources.value[0].id)
|
||
} catch (error: any) {
|
||
useMessage().error(error?.msg || "读取可用打印终端失败")
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
/** 校验、二次确认并提交任务;响应只代表任务受理,不代表打印机已经出纸。 */
|
||
const submit = async () => {
|
||
const valid = await formRef.value?.validate?.().catch(() => false)
|
||
if (valid === false) return
|
||
if (requiresReason.value) {
|
||
try {
|
||
await useMessageBox().confirm(`确认提交第 ${Number(options.printNo || 0) + 1} 次打印?提交后将保留操作原因。`)
|
||
} catch {
|
||
return
|
||
}
|
||
}
|
||
submitting.value = true
|
||
try {
|
||
if (options.action === "retry") {
|
||
await hardInvoiceApi.retry({ historyId: options.taskId, sourceHardId: form.sourceHardId || undefined })
|
||
} else if (options.action === "reprint") {
|
||
await hardInvoiceApi.reprint({ historyId: options.taskId, reprintReason: form.reprintReason.trim() })
|
||
} else {
|
||
await hardInvoiceApi.print({
|
||
orderId: options.orderId,
|
||
printType: options.printType,
|
||
sourceHardId: form.sourceHardId || undefined,
|
||
reprintReason: form.reprintReason.trim() || undefined,
|
||
})
|
||
}
|
||
visible.value = false
|
||
useMessage().success("打印任务已提交")
|
||
emit("submitted")
|
||
} catch (error: any) {
|
||
useMessage().error(error?.msg || "打印任务提交失败")
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
defineExpose({ open })
|
||
</script>
|