|
@@ -0,0 +1,191 @@
|
|
|
|
+<script setup lang="ts">
|
|
|
|
+import { type FormInstance, showToast } from 'vant';
|
|
|
|
+import { tryOnMounted, tryOnUnmounted } from '@vueuse/core';
|
|
|
|
+import { platformIsPDA } from '@/platform';
|
|
|
|
+import { usePrintStore } from '@/stores';
|
|
|
|
+import { useSnapshot } from '@/module/print/useSnapshot.ts';
|
|
|
|
+import { regex } from '@/tools/validator.ts';
|
|
|
|
+import { browserPrint } from '@/stores/print.store.ts';
|
|
|
|
+
|
|
|
|
+const props = defineProps<{
|
|
|
|
+ disabled?: boolean;
|
|
|
|
+ loader: () => Promise<{ default: Component }>;
|
|
|
|
+ loaderParams: Record<string, any>;
|
|
|
|
+ onPrintBefore?: () => boolean | Promise<boolean>;
|
|
|
|
+ onPrintAfter?: () => void | Promise<void>;
|
|
|
|
+}>();
|
|
|
|
+const emits = defineEmits<{ start: [] }>();
|
|
|
|
+
|
|
|
|
+const Printer = usePrintStore();
|
|
|
|
+
|
|
|
|
+watch(
|
|
|
|
+ () => props.loaderParams,
|
|
|
|
+ (value, oldValue) => {
|
|
|
|
+ if (JSON.stringify(value) !== JSON.stringify(oldValue)) snapshot.value = '';
|
|
|
|
+ },
|
|
|
|
+ { immediate: false },
|
|
|
|
+);
|
|
|
|
+
|
|
|
|
+const printing = defineModel<boolean>('loading', { default: false });
|
|
|
|
+const total = defineModel('total', { default: 1 });
|
|
|
|
+const host = defineModel('host', { default: '' });
|
|
|
|
+
|
|
|
|
+const show = ref(false);
|
|
|
|
+const connecting = ref(false);
|
|
|
|
+
|
|
|
|
+const rules = reactive({
|
|
|
|
+ host: [
|
|
|
|
+ { required: true, message: '请输入打印机 IP 地址' },
|
|
|
|
+ {
|
|
|
|
+ validator: (value: string) => regex.host.test(value),
|
|
|
|
+ message: `IP 地址格式不正确`,
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ total: [{ required: true, message: '请输入打印份数' }],
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+const { snapshot, tspl, render } = useSnapshot(props.loader);
|
|
|
|
+
|
|
|
|
+const open = async () => {
|
|
|
|
+ if ((await props.onPrintBefore?.()) ?? true) {
|
|
|
|
+ printing.value = true;
|
|
|
|
+ try {
|
|
|
|
+ const { snapshot: src } = await render(props.loaderParams);
|
|
|
|
+ if (platformIsPDA()) show.value = true;
|
|
|
|
+ else browserPrint(src);
|
|
|
|
+ } catch (error: any) {
|
|
|
|
+ showNotify({ message: `创建打印数据错误(${error?.message})`, type: 'danger' });
|
|
|
|
+ }
|
|
|
|
+ printing.value = false;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const form = useTemplateRef<FormInstance>('printer-form');
|
|
|
|
+const validate = (name?: string) =>
|
|
|
|
+ form.value?.validate(name).then(
|
|
|
|
+ () => true,
|
|
|
|
+ () => false,
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+const start = (action: 'cancel' | 'confirm') => {
|
|
|
|
+ if (action === 'confirm') return validate()?.then((valid) => valid && print()) ?? false;
|
|
|
|
+ if (action === 'cancel') {
|
|
|
|
+ printing.value = false;
|
|
|
|
+ form.value?.resetValidation?.()
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+async function print() {
|
|
|
|
+ if (platformIsPDA()) {
|
|
|
|
+ if (connecting.value || !host.value) return false;
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ const data = tspl.value?.(total.value);
|
|
|
|
+ await Printer.print(host.value, data!!);
|
|
|
|
+ showToast({ message: `开始打印`, type: 'success' });
|
|
|
|
+ } catch (error: any) {
|
|
|
|
+ showNotify({ message: `创建打印任务错误(${error?.message})`, type: 'danger' });
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const connected = ref(false);
|
|
|
|
+
|
|
|
|
+async function toggle() {
|
|
|
|
+ const valid = await validate('host');
|
|
|
|
+ if (!valid) return;
|
|
|
|
+
|
|
|
|
+ connecting.value = true;
|
|
|
|
+ try {
|
|
|
|
+ const device = Printer.getDevice(host.value);
|
|
|
|
+ if (connected.value) {
|
|
|
|
+ await Printer.disconnect(device);
|
|
|
|
+ showToast({ message: '断开成功', type: 'success' });
|
|
|
|
+ connected.value = false;
|
|
|
|
+ } else {
|
|
|
|
+ await Printer.connect(device);
|
|
|
|
+ connected.value = true;
|
|
|
|
+ showToast({ message: '连接成功', type: 'success' });
|
|
|
|
+ }
|
|
|
|
+ } catch (error: any) {
|
|
|
|
+ showNotify({ message: `操作失败(${error?.message})`, type: 'warning' });
|
|
|
|
+ connected.value = false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ connecting.value = false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+let connectListener: Array<() => void> = [];
|
|
|
|
+tryOnMounted(() => {
|
|
|
|
+ if (!host.value) host.value = Printer.host || '';
|
|
|
|
+ if (!connected.value) toggle();
|
|
|
|
+ if (platformIsPDA()) {
|
|
|
|
+ connectListener.push(
|
|
|
|
+ window.bridge.addEventListener('print:connect', () => {
|
|
|
|
+ connected.value = true;
|
|
|
|
+ }),
|
|
|
|
+ window.bridge.addEventListener('print:disconnect', () => {
|
|
|
|
+ connected.value = false;
|
|
|
|
+ }),
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+});
|
|
|
|
+tryOnUnmounted(() => {
|
|
|
|
+ connectListener.forEach((fn) => fn?.());
|
|
|
|
+ connectListener = [];
|
|
|
|
+ snapshot.value = '';
|
|
|
|
+});
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<template>
|
|
|
|
+ <van-dialog v-model:show="show" title="连接打印机" :confirm-button-disabled="connecting" confirm-button-text="打印" show-cancel-button :before-close="start">
|
|
|
|
+ <van-form ref="printer-form" @submit="print">
|
|
|
|
+ <van-cell-group>
|
|
|
|
+ <van-field
|
|
|
|
+ name="host"
|
|
|
|
+ label="打印设置"
|
|
|
|
+ placeholder="打印机 IP 地址"
|
|
|
|
+ v-model="host"
|
|
|
|
+ :rules="rules['host']"
|
|
|
|
+ :readonly="connecting"
|
|
|
|
+ :disabled="connected"
|
|
|
|
+ enterkeyhint="go"
|
|
|
|
+ @keydown.enter="toggle()"
|
|
|
|
+ >
|
|
|
|
+ <template #button>
|
|
|
|
+ <van-button size="small" :type="connected ? 'danger' : 'primary'" :loading="connecting" @click="toggle">
|
|
|
|
+ {{ connected ? '断开' : '连接' }}
|
|
|
|
+ </van-button>
|
|
|
|
+ </template>
|
|
|
|
+ </van-field>
|
|
|
|
+ <van-field class="suffix" v-model="total" name="total" :rules="rules['total']" type="digit" label="打印包数" placeholder="请输入" :min="1">
|
|
|
|
+ <template #extra>
|
|
|
|
+ <div v-if="total">包</div>
|
|
|
|
+ </template>
|
|
|
|
+ </van-field>
|
|
|
|
+ <van-cell title="温馨提示" label="输入几包就打印几张标签" />
|
|
|
|
+ </van-cell-group>
|
|
|
|
+ </van-form>
|
|
|
|
+ </van-dialog>
|
|
|
|
+ <van-button block :loading="printing" :disabled="props.disabled" @click="open()">打印标签</van-button>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
+.van-field.suffix {
|
|
|
|
+ :deep(.van-field__value) {
|
|
|
|
+ flex: none;
|
|
|
|
+ //width: min-content;
|
|
|
|
+ //min-width: 120px;
|
|
|
|
+ max-width: 170px;
|
|
|
|
+
|
|
|
|
+ input {
|
|
|
|
+ text-align: center;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+:deep(.van-field__label) {
|
|
|
|
+ align-self: center;
|
|
|
|
+}
|
|
|
|
+</style>
|