| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705 |
- <!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page;推荐使用json5,更强大,且允许注释 -->
- <route lang="json5">
- {
- style: {
- navigationBarTitleText: '远程打印',
- },
- }
- </route>
- <template>
- <view class="page-form">
- <!-- 选择打印机--获取打印参数--上传打印 -->
- <wd-form ref="form" :model="formData">
- <wd-cell-group custom-class="form-group" border>
- <wd-cell title="上传文件"
- title-width="100px"
- required
- prop="fileList"
- custom-class="my-cell">
- <view class="file-list">
- <view v-for="(item, index) of fileList"
- :key="item.filePath"
- class="file-item"
- @click="previewFile(item.name, item.filePath)">
- <view class="item-icon">
- <wd-img
- :src="getFileIcon(item.name)"
- :width="20"
- :height="20"
- mode="aspectFit"
- ></wd-img>
- </view>
- <view class="item-name">{{ item.name }}</view>
- <view class="item-del" @click.stop="removeFile(index)">
- <wd-img
- :src="delSrc"
- :width="20"
- :height="20"
- mode="aspectFit"
- ></wd-img>
- </view>
- </view>
- <!-- 选择文件: 支持多选多个 (系统文件另外传入) -->
- <wd-button v-if="accept != 'all'" size="small" @click="selectFile()">选择文件</wd-button>
- </view>
- </wd-cell>
- <wd-select-picker
- v-model="formData.printer"
- label="打印机"
- label-width="100px"
- type="radio"
- :columns="printerList"
- value-key="id"
- label-key="showName"
- :max="1"
- :show-confirm="false"
- filterable
- placeholder="请选择打印机"
- prop="printer"
- :rules="[{ required: true, message: '请选择打印机' }]"
- @change="handlePrinterChange"
- />
- <!-- 动态属性 -->
- <template v-for="option of printerOptions" :key="option.key">
- <!-- type: select 下拉选项 -->
- <wd-select-picker
- v-if="option.type == 'select'"
- v-model="formData[option.key]"
- :label="option.text"
- label-width="100px"
- type="radio"
- :columns="option.values"
- value-key="key"
- label-key="text"
- :max="1"
- :show-confirm="false"
- filterable
- :placeholder="`请选择${option.text}`"
- :prop="option.key"
- :rules="[{ required: true, message: `请选择${option.text}` }]"
- />
- <!-- type: radio 单选项 -->
- <wd-cell v-if="option.type == 'radio'"
- :title="option.text"
- title-width="100px"
- custom-class="my-cell"
- :prop="option.key"
- :rules="[{ required: true, message: `请选择${option.text}` }]">
- <wd-radio-group v-model="formData[option.key]"
- shape="dot"
- inline>
- <wd-radio v-for="item of option.values"
- :key="item.key"
- :value="item.key">
- {{ item.text }}
- </wd-radio>
- </wd-radio-group>
- </wd-cell>
- <!-- type: rang 范围值 传值 方式为str,end -->
- <wd-cell v-if="option.type == 'rang'"
- :title="option.text"
- title-width="100px"
- custom-class="my-cell"
- :prop="option.key">
- <!-- TODO: 需增加 option.min option.max 控制; 默认全部 -->
- <!-- 输入框: 从 xx 到 xx -->
- <view style="text-align: left">
- <view class="inline-txt" style="margin-left: 0">从</view>
- <wd-input
- no-border
- custom-style="display: inline-block; width: 70px; vertical-align: middle"
- placeholder="起始值"
- v-model="formData[option.key][0]"
- />
- <view class="inline-txt">到</view>
- <wd-input
- no-border
- custom-style="display: inline-block; width: 70px; vertical-align: middle"
- placeholder="结束值"
- v-model="formData[option.key][1]"
- />
- </view>
- </wd-cell>
- <!-- type: number 数字 -->
- <wd-cell v-if="option.type == 'number'"
- :title="option.text"
- title-width="100px"
- custom-class="my-cell"
- :rules="[{ required: true, message: `请输入${option.text}` }]">
- <wd-input-number v-model="formData[option.key]"
- :min="option.min"
- :max="option.max"
- :step="1"
- step-strictly
- input-width="100px" />
- </wd-cell>
- </template>
- </wd-cell-group>
- <view class="form-footer">
- <wd-button type="primary" size="large" block @click="handleSubmit()">确认打印</wd-button>
- </view>
- </wd-form>
- </view>
- </template>
- <script lang="ts" setup>
- import { useToast, useMessage } from 'wot-design-uni'
- import { getUserHubPrints, getUserHubAttr, getInvoiceBatch } from '@/service/api'
- import { getEnvBaseUrl, redirectToUpload, reLaunchToHome } from '@/utils'
- import { useUserStore } from '@/store'
- const userStore = useUserStore()
- const token = userStore.token
- const baseUrl = getEnvBaseUrl()
- const toast = useToast()
- const message = useMessage()
- // 系统文件--all, 聊天文件--msg, 相册--image, 发票--invoice
- const accept = ref("all")
- const fileList = ref([])
- const form = ref()
- const formData: any = ref({
- printer: "",
- })
- const printerList: any = ref([])
- const printerOptions: any = ref([])
- const allowType = ['txt', 'pdf', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'pptx', 'gif', 'png', 'jpg', 'jpeg', 'webp']
- const maxSize = 10 * 1024 * 1024
- // 文件类型
- const imageSrc = '/static/images/image.png'
- const docSrc = '/static/images/doc.png'
- const xlsSrc = '/static/images/xls.png'
- const pptSrc = '/static/images/ppt.png'
- const pdfSrc = '/static/images/pdf.png'
- const txtSrc = '/static/images/txt.png'
- const otherSrc = '/static/images/other.png'
- const delSrc = '/static/images/deleteIcon.png'
- // 文件类型图片匹配
- const srcMap = {
- 'doc': docSrc,
- 'docx': docSrc,
- 'xls': xlsSrc,
- 'xlsx': xlsSrc,
- 'ppt': pptSrc,
- 'pptx': pptSrc,
- 'png': imageSrc,
- 'jpg': imageSrc,
- 'jpeg': imageSrc,
- 'gif': imageSrc,
- 'webp': imageSrc,
- 'txt': txtSrc,
- 'pdf': pdfSrc,
- }
- // 上传成功的任务
- const successPrint = ref([])
- // 上传失败的任务
- const failedFiles = ref([])
- defineOptions({
- name: 'Print',
- })
- // 获取打印机
- function getPrinterList() {
- let params = { id: "" }
- getUserHubPrints(params)
- .then((res: any) => {
- if (res.code === 0 && res.body) {
- printerList.value = res.body || []
- printerList.value.forEach(item => {
- item.showName = `${item.name} (${item.userHubName})`
- })
- }
- })
- .catch((e) => {})
- }
- // 处理更换打印机
- function handlePrinterChange(item: any) {
- printerOptions.value = []
- formData.value = {
- printer: item.value
- }
- let obj = printerList.value.find(i => i.id == item.value) || {}
- let params = {
- id: obj.userHubId,
- printer: obj.name,
- }
- getUserHubAttr(params)
- .then((res: any) => {
- if (res.code === 0 && res.body) {
- printerOptions.value = res.body || []
- let _formData = {
- printer: item.value,
- userHubId: obj.userHubId,
- printerName: obj.name,
- }
- if (printerOptions.value.length) {
- printerOptions.value.forEach(option => {
- switch(option.type) {
- case "rang":
- _formData[option.key] = option.def ? option.def.split(',') : [option.min, '']
- break;
- default:
- _formData[option.key] = option.def
- }
- });
- formData.value = _formData
- }
- }
- })
- .catch((e) => {
- })
- .finally(() => {})
- }
- // 获取文件格式
- function getFileType(fileName) {
- let type = ""
- const lastIndex = fileName.lastIndexOf('.')
- if (lastIndex != -1) {
- type = fileName.substring(lastIndex + 1)
- }
- return type
- }
- // 获取文件图标
- function getFileIcon(fileName) {
- return srcMap[getFileType(fileName)] || otherSrc
- }
- // 处理选择文件
- function selectFile() {
- switch(accept.value) {
- case "msg":
- uni.chooseMessageFile({
- count: 10,
- type: "all",
- success (res) {
- console.log('chooseMessageFile res: ', res);
- if (res.errMsg == "chooseMessageFile:ok") {
- let failList = []
- res.tempFiles.forEach(item => {
- if (item.size > maxSize || !allowType.includes(getFileType(item.name))) {
- failList.push(item)
- } else {
- fileList.value.push({
- name: item.name,
- filePath: item.path,
- })
- }
- })
- if (failList.length > 0) toast.warning(`文件大小限制为10MB, 文件格式仅支持 ${allowType.join('、')}, 所选文件有 ${failList.length} 格式不符合`)
- }
- },
- fail () {
- toast.warning("选择微信文件失败, 请重试")
- }
- })
- break;
- case "image":
- uni.chooseImage({
- count: 10,
- sourceType: ['album', 'camera'],
- sizeType: ['original', 'compressed'],
- extension: ['png', 'jpg', 'jpeg', 'gif', 'webp'],
- success (res) {
- console.log('chooseImage res: ', res);
- if (res.errMsg == "chooseImage:ok") {
- let failList = []
- res.tempFiles.forEach((item, index) => {
- if (item.size > maxSize) {
- failList.push(item)
- } else {
- fileList.value.push({
- // name: item.path.substring(item.path.lastIndexOf("/") + 1),
- name: `图片-${new Date().getTime() + index}.${getFileType(item.path)}`,
- filePath: item.path,
- })
- }
- })
- if (failList.length > 0) toast.warning(`文件大小限制为10MB, 所选文件有 ${failList.length} 格式不符合`)
- }
- },
- fail () {
- toast.warning("选择相册图片失败, 请重试")
- }
- })
- break;
- case "invoice":
- uni.chooseInvoice({
- success (res) {
- if (res.errMsg == "chooseInvoice:ok") {
- let list = JSON.parse(res.invoiceInfo)
- if (list && list.length > 0) {
- let params = {
- cardId: list.map(i => i.card_id).join(","),
- encryptCode: list.map(i => i.encrypt_code).join(","),
- }
- getInvoiceBatch(params).then(res => {
- if (res.code == 0 && res.body) {
- console.log('InvoiceBatch res.body: ', res.body);
- // 遍历下载pdf 存储在 fileList 打印
- res.body.forEach(item => {
- downloadFile(`发票-${item.payee}.png`, item.userInfo.pdfUrl)
- });
- }
- })
- }
- }
- },
- fail () {
- toast.warning("选择微信发票失败, 请重试")
- }
- })
- break;
- }
- }
- // 下载文件(用于处理发票)
- function downloadFile(fileName, fileUrl) {
- uni.downloadFile({
- url: fileUrl,
- filePath: uni.env.USER_DATA_PATH + '/' + fileName,
- success: (res) => {
- fileList.value.push({
- name: fileName,
- filePath: res.filePath,
- })
- },
- fail () {
- toast.warning("下载文件失败, 请重试")
- }
- })
- }
- // 预览文件 (区分文件&图片)
- function previewFile(fileName, filePath) {
- let fileType = getFileType(fileName)
- if (['png', 'jpg', 'jpeg', 'webp'].includes(fileType)) {
- // 图片预览
- let imgUrl = filePath
- uni.previewImage({
- current: 0,
- urls: [imgUrl],
- })
- } else {
- // 文件预览
- uni.openDocument({
- filePath: filePath,
- success () {},
- fail () {
- toast.warning("打开文件失败, 请重试")
- }
- })
- }
- }
- // 移除文件
- function removeFile(index) {
- fileList.value.splice(index, 1)
- if (accept.value == "all" && fileList.value.length == 0) {
- message.confirm({
- msg: "文件已全部移除, 是否重新选择?",
- closeOnClickModal: false,
- }).then(() => {
- redirectToUpload()
- }).catch((error) => {
- reLaunchToHome()
- })
- }
- }
- // 批量打印文件
- async function handleBatchPrint() {
- try {
- failedFiles.value = []; // 记录上传失败的文件
- for (let i = 0; i < fileList.value.length; i++) {
- const fileName = fileList.value[i].name;
- const filePath = fileList.value[i].filePath;
- toast.loading({
- loadingType: 'ring',
- msg: `开始上传文件 ${i + 1}: ${fileName}`
- })
- try {
- // 上传文件并监听进度
- const uploadRes = await handlePrint(filePath, (progress) => {
- toast.loading({
- loadingType: 'ring',
- msg: `文件 ${i + 1} 上传进度: ${progress}%`
- })
- });
- toast.success(`文件 ${i + 1} 上传成功`)
- } catch (error) {
- toast.error(`文件 ${i + 1} 上传失败`)
- failedFiles.value.push(fileList.value[i]); // 记录失败的文件
- }
- }
- // 检查是否有失败的文件
- if (failedFiles.value.length > 0) {
- message.confirm({
- title: '上传失败',
- msg: `有 ${failedFiles.value.length} 个文件上传失败,是否重新上传?`,
- closeOnClickModal: false,
- }).then(async () => {
- await retryUploadFiles(failedFiles.value); // 重新上传失败的文件
- }).catch((error) => {
- msgConfirm("上传完成", "取消重新上传, 其余文件已上传成功!")
- })
- } else {
- toast.success("所有文件上传成功")
- msgConfirm("上传完成", "所有文件上传成功")
- }
- } catch(error) {
- console.log("上传过程中发生错误", error);
- }
- }
- async function retryUploadFiles(failedFiles) {
- const newFailedFiles = []; // 记录重新上传失败的文件
- // 逐个重新上传失败的文件
- for (let i = 0; i < failedFiles.length; i++) {
- const fileName = failedFiles[i].name;
- const filePath = failedFiles[i].filePath;
- toast.loading({
- loadingType: 'ring',
- msg: `重新上传文件 ${i + 1}: ${fileName}`
- })
- try {
- const uploadRes = await handlePrint(filePath, (progress) => {
- toast.loading({
- loadingType: 'ring',
- msg: `文件 ${i + 1} 上传进度: ${progress}%`
- })
- });
- toast.success(`文件 ${i + 1} 重新上传成功`)
- } catch (error) {
- toast.error(`文件 ${i + 1} 重新上传成功`)
- newFailedFiles.push(failedFiles[i]); // 记录重新上传失败的文件
- }
- }
- // 检查是否还有失败的文件
- if (newFailedFiles.length > 0) {
- // 修改当前文件列表
- fileList.value = newFailedFiles
- message.alert({
- title: '重新上传失败',
- msg: `有 ${newFailedFiles.length} 个文件重新上传失败,请检查网络或文件后重试。`,
- })
- } else {
- toast.success("所有失败文件重新上传成功")
- msgConfirm("上传完成", "所有失败文件重新上传成功!")
- }
- }
- // 打印文件 (单个打印任务)
- function handlePrint(filePath, onProgress) {
- return new Promise((resolve, reject) => {
- // 构造提交数据
- let params = {
- id: formData.value.userHubId,
- printer: formData.value.printerName,
- }
- printerOptions.value.forEach(option => {
- switch(option.type) {
- case "rang":
- // 打印页数范围不传
- // params[option.key] = formData.value[option.key].join(",")
- break;
- default:
- params[option.key] = formData.value[option.key]
- }
- })
- const queryParams = Object.entries(params).map(([key, value]) => {
- return `${key}=${encodeURIComponent(value)}`;
- }).join('&');
- const uploadTask = uni.uploadFile({
- url: `${baseUrl}/sys/wx/userHub/print?${queryParams}`, // 上传接口地址
- filePath: filePath,
- name: 'file', // 文件对应的 key
- formData: {
- // 其他表单数据
- },
- success: (res) => {
- if (res.statusCode === 200 && JSON.parse(res.data).code === 0) {
- resolve(res.data);
- } else {
- reject(new Error(`上传失败,状态码: ${res.statusCode}`));
- }
- },
- fail: (err) => {
- reject(err);
- },
- });
- // 监听上传进度
- uploadTask.onProgressUpdate((res) => {
- if (onProgress) {
- onProgress(res.progress); // 回调上传进度
- }
- });
- });
- }
- // 处理提交打印
- function handleSubmit() {
- if (fileList.value.length == 0) {
- toast.warning('请先上传文件')
- return
- }
- form.value.validate().then(({ valid }) => {
- if (valid) {
- // 不区分单个/批量, 以批量打印处理
- handleBatchPrint()
- }
- })
- }
- // 打印完成后提示词
- function msgConfirm(title="提示", msg="文件已上传成功!") {
- message.confirm({
- title,
- msg,
- confirmButtonText: "查看任务",
- cancelButtonText: "继续打印",
- }).then(() => {
- // 查看任务
- uni.redirectTo({
- url: `/pages/print/job?tab=0`,
- })
- }).catch(() => {
- // 继续打印
- failedFiles.value = []
- fileList.value = []
- printerOptions.value = []
- formData.value = {
- printer: ""
- }
- if (accept.value == "all") {
- redirectToUpload()
- }
- })
- }
- // 处理系统文件webview传入
- function base64ToTempFilePath(fileName, base64Data, success, fail) {
- const fs = uni.getFileSystemManager()
- // const fileName = 'temp_' + Date.now() + '.png' // 自定义文件名,可根据需要修改
- const filePath = uni.env.USER_DATA_PATH + '/' + fileName
- const buffer = uni.base64ToArrayBuffer(base64Data)
- fs.writeFile({
- filePath,
- data: buffer,
- encoding: 'binary',
- success() {
- success && success(filePath)
- },
- fail() {
- fail && fail()
- }
- })
- }
- // 打开SSE webview页面
- function openWebViewSSE() {
- // let list = JSON.stringify(fileList.value.map(item => item.name).join(",") || "")
- console.log('list: ', fileList.value);
- let list = JSON.stringify(fileList.value)
- const url = `https://service.1ai.ltd/webview-sse/index.html?token=${token}&list=${list}&t=${new Date().getTime()}`
- uni.navigateTo({
- url: `/pages/webview/sse?url=${encodeURIComponent(url)}`,
- })
- }
- onLoad((option) => {
- if (option && option.accept) {
- accept.value = option.accept
- // 处理系统文件--all: 从webview上传的文件
- if (option.accept == "all" && uni.getStorageSync('fileList')) {
- fileList.value = uni.getStorageSync('fileList');
- uni.removeStorageSync('fileList');
- fileList.value.forEach(item => {
- item.file = item.file.split('base64,')[1]
- base64ToTempFilePath(item.name, item.file, (filePath) => {
- console.log('转换成功,临时地址为:', filePath)
- item.filePath = filePath
- }, function() {
- toast.warning('文件转换失败,请重试')
- })
- })
- }
- }
- getPrinterList()
- })
- </script>
- <style lang="scss" scoped>
- :deep(.my-cell) {
- .wd-cell__value {
- text-align: left !important;
- }
- }
- :deep(.wd-radio-group) {
- padding: 0 !important;
- font-size: unset !important;
- .wd-radio {
- padding-top: 0 !important;
- }
- }
- :deep(.wd-upload__mask) {
- display: none !important;
- }
- :deep(.img-btn .wd-img__image) {
- width: 160rpx;
- height: 160rpx;
- border-radius: 40rpx;
- }
- .file-item {
- margin-bottom: 8rpx;
- display: flex;
- align-items: center;
- .item-icon {
- display: flex;
- width: 40rpx;
- height: 40rpx;
- margin-right: 8rpx;
- }
- .item-name {
- flex: 1;
- word-break: break-all;
- }
- .item-del {
- display: flex;
- width: 40rpx;
- height: 40rpx;
- margin-left: 8rpx;
- }
- }
- .inline-txt {
- display: inline-block;
- font-size: 28rpx;
- margin: 0 16rpx;
- color: rgba(0, 0, 0, 0.45);
- vertical-align: middle;
- }
- </style>
|