Просмотр исходного кода

优化文件打印(4个来源)

“shengjie.huang” 1 год назад
Родитель
Сommit
88c7aba1b7

+ 8 - 1
src/pages.json

@@ -110,12 +110,19 @@
         "navigationBarTitleText": "打印机列表"
         "navigationBarTitleText": "打印机列表"
       }
       }
     },
     },
+    {
+      "path": "pages/test/index",
+      "type": "page",
+      "style": {
+        "navigationBarTitleText": "测试页"
+      }
+    },
     {
     {
       "path": "pages/webview/index",
       "path": "pages/webview/index",
       "type": "page",
       "type": "page",
       "layout": "default",
       "layout": "default",
       "style": {
       "style": {
-        "navigationBarTitleText": "webView"
+        "navigationBarTitleText": "上传文件"
       }
       }
     }
     }
   ],
   ],

+ 1 - 1
src/pages/assistant/index.vue

@@ -178,7 +178,7 @@ function handleDelete() {
 // 跳转到打印机列表
 // 跳转到打印机列表
 function toPrinter() {
 function toPrinter() {
   uni.navigateTo({
   uni.navigateTo({
-    url: `/pages/printer/index?id=${currentData.value.id || ''}`,
+    url: `/pages/printer/index?id=${currentData.value.id || ''}&asname=${currentData.value.asname}`,
   })
   })
 }
 }
 
 

+ 12 - 4
src/pages/index/index.vue

@@ -33,7 +33,7 @@
           <view class="item-title">打印助手</view>
           <view class="item-title">打印助手</view>
           <view class="item-desc">我的打印助手</view>
           <view class="item-desc">我的打印助手</view>
         </view>
         </view>
-        <view class="guide-item" @click="toPrint('all')">
+        <view class="guide-item" @click="openWebView()">
           <wd-img :src="fileImgSrc" width="100" mode="widthFix"></wd-img>
           <wd-img :src="fileImgSrc" width="100" mode="widthFix"></wd-img>
           <view class="item-title">远程打印</view>
           <view class="item-title">远程打印</view>
           <view class="item-desc">无需驱动 隔空打印</view>
           <view class="item-desc">无需驱动 隔空打印</view>
@@ -55,13 +55,13 @@
             </view>
             </view>
           </view>
           </view>
           <view class="item">
           <view class="item">
-            <view class="title" @click="developing()">
+            <view class="title" @click="toPrint('msg')">
               <wd-img :src="wechatFileSrc" width="40" mode="widthFix"></wd-img>
               <wd-img :src="wechatFileSrc" width="40" mode="widthFix"></wd-img>
-              <view class="title-text">微信图片</view>
+              <view class="title-text">微信文件</view>
             </view>
             </view>
           </view>
           </view>
           <view class="item">
           <view class="item">
-            <view class="title" @click="developing()">
+            <view class="title" @click="toPrint('invoice')">
               <wd-img :src="wechatInvoiceSrc" width="40" mode="widthFix"></wd-img>
               <wd-img :src="wechatInvoiceSrc" width="40" mode="widthFix"></wd-img>
               <view class="title-text">微信发票</view>
               <view class="title-text">微信发票</view>
             </view>
             </view>
@@ -130,6 +130,7 @@ function initSwiperList() {
 // uni.navigateTo({
 // uni.navigateTo({
 //   url: `/pages/map/index`,
 //   url: `/pages/map/index`,
 // })
 // })
+// redirectTo
 
 
 function toUserHub() {
 function toUserHub() {
   if (isLogined.value) {
   if (isLogined.value) {
@@ -142,6 +143,13 @@ function toUserHub() {
   }
   }
 }
 }
 
 
+function openWebView() {
+  const url = "https://service.1ai.ltd/upload-file/index.html"
+  uni.navigateTo({
+    url: `/pages/webview/index?url=${encodeURIComponent(url)}`,
+  })
+}
+
 function toPrint(accept) {
 function toPrint(accept) {
   if (isLogined.value) {
   if (isLogined.value) {
     uni.navigateTo({
     uni.navigateTo({

+ 16 - 0
src/pages/personal/index.vue

@@ -21,6 +21,11 @@
       <!-- <view class="head-name">{{ userInfo.name }}</view> -->
       <!-- <view class="head-name">{{ userInfo.name }}</view> -->
     </view>
     </view>
     <view class="main-content mt6">
     <view class="main-content mt6">
+      <!-- TODO: 测试 -->
+      <!-- <view class="content-item line-b" @click="toTest">
+        <view>测试页</view>
+        <wd-icon name="arrow-right" size="20px" color="#808080"></wd-icon>
+      </view> -->
       <view class="content-item line-b" @click="toAddUserHub">
       <view class="content-item line-b" @click="toAddUserHub">
         <view>添加打印助手</view>
         <view>添加打印助手</view>
         <wd-icon name="arrow-right" size="20px" color="#808080"></wd-icon>
         <wd-icon name="arrow-right" size="20px" color="#808080"></wd-icon>
@@ -100,6 +105,17 @@ function initFn() {
   userInfo.avatar = "/static/images/avatar.png"
   userInfo.avatar = "/static/images/avatar.png"
 }
 }
 
 
+function toTest() {
+  if (isLogined.value) {
+    uni.navigateTo({
+      url: `/pages/test/index`,
+    })
+  } else {
+    toast.warning('请先前往登录')
+    toLogin(1500)
+  }
+}
+
 onLoad(() => {
 onLoad(() => {
   initFn()
   initFn()
 })
 })

+ 380 - 194
src/pages/print/index.vue

@@ -11,23 +11,39 @@
   <view class="page-form">
   <view class="page-form">
     <!-- 选择打印机--获取打印参数--上传打印 -->
     <!-- 选择打印机--获取打印参数--上传打印 -->
     <wd-form ref="form" :model="formData">
     <wd-form ref="form" :model="formData">
-      <!-- :rules=rules -->
       <wd-cell-group custom-class="form-group" border>
       <wd-cell-group custom-class="form-group" border>
-        <wd-cell :title="`上传${accept == 'image' ? '图片' : '文件'}`"
+        <wd-cell title="上传文件"
                  title-width="100px"
                  title-width="100px"
                  required
                  required
-                 prop="fileList">
-          <!-- :rules="[{ required: true, message: '请上传图片', validator: validateFileList }]" -->
-          <wd-upload
-            ref="uploader"
-            :accept="accept"
-            :auto-upload="false"
-            :limit="1"
-            :file-list="fileList"
-            :before-upload="beforeUpload"
-            :upload-method="handleUpload"
-            @change="handleFileChange"
-          ></wd-upload>
+                 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.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-cell>
 
 
         <wd-select-picker
         <wd-select-picker
@@ -37,7 +53,7 @@
           type="radio"
           type="radio"
           :columns="printerList"
           :columns="printerList"
           value-key="id"
           value-key="id"
-          label-key="name"
+          label-key="showName"
           :max="1"
           :max="1"
           :show-confirm="false"
           :show-confirm="false"
           filterable
           filterable
@@ -88,14 +104,8 @@
                    title-width="100px"
                    title-width="100px"
                    custom-class="my-cell"
                    custom-class="my-cell"
                    :prop="option.key">
                    :prop="option.key">
-            <!-- :rules="[{ required: true, message: `请选择${option.text}` }]" -->
-
-            <!-- 滑块 -->
-            <!-- <wd-slider v-model="formData[option.key]"
-                       :min="option.min"
-                       :max="option.max" /> -->
-
-            <!-- 输入框: 从 xx 到 xx (TODO: 需增加 min max 控制) -->
+            <!-- TODO: 需增加 option.min option.max 控制; 默认全部 -->
+            <!-- 输入框: 从 xx 到 xx -->
             <view style="text-align: left">
             <view style="text-align: left">
               <view class="inline-txt" style="margin-left: 0">从</view>
               <view class="inline-txt" style="margin-left: 0">从</view>
               <wd-input
               <wd-input
@@ -133,73 +143,301 @@
         <wd-button type="primary" size="large" block @click="handleSubmit()">确认打印</wd-button>
         <wd-button type="primary" size="large" block @click="handleSubmit()">确认打印</wd-button>
       </view>
       </view>
     </wd-form>
     </wd-form>
+
+    <!-- 当前打印任务 及 实时状态 -->
+    <!-- 连接SSE: /print/see/connection -->
   </view>
   </view>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import { useToast, useMessage } from 'wot-design-uni'
 import { useToast, useMessage } from 'wot-design-uni'
-import { getUserHubPrints, getUserHubAttr, printFile } from '@/service/api'
-import { isArray } from 'wot-design-uni/components/common/util'
-import FormData from '@/utils/formData'
-import { getEnvBaseUrl } from '@/utils'
+import { getUserHubPrints, getUserHubAttr, getInvoiceInfo, getInvoiceBatch } from '@/service/api'
+import { getEnvBaseUrl, redirectToUpload, reLaunchToHome } from '@/utils'
 
 
 const baseUrl = getEnvBaseUrl()
 const baseUrl = getEnvBaseUrl()
 const toast = useToast()
 const toast = useToast()
 const message = useMessage()
 const message = useMessage()
+// 系统文件--all, 聊天文件--msg, 相册--image, 发票--invoice
 const accept = ref("all")
 const accept = ref("all")
-const uploader = ref()
 const fileList = ref([])
 const fileList = ref([])
+// 测试发票
+// const fileList = ref([
+//   {name: "测试发票.pdf", filePath: "https://pay.weixin.qq.com/invoicing/invoicepdf/getpdf?type=invoice&card_id=pmB7twzboHW_C66OLCIqz4VYkpjw&wx_invoice_token=FCFimpsJEytCHNpnEzqafLRmT_5NV5Mk0evaCTOrJeB3J_EI2Wvn0jSQAJnMrd0xVG8vxpcfhpeNOSn5XGEYfg.%3D"}
+// ])
 const form = ref()
 const form = ref()
 const formData: any = ref({
 const formData: any = ref({
   printer: "",
   printer: "",
 })
 })
 const printerList: any = ref([])
 const printerList: any = ref([])
 const printerOptions: any = ref([])
 const printerOptions: any = ref([])
-const hasFile = ref(false)
+const allowType = ['txt', 'pdf', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'pptx', 'gif', 'png', 'jpg', 'jpeg', 'webp']
+const maxSize = 10 * 1024 * 1024
 
 
-// TODO: 优化上传文件校验
-// const rules = {
-//   fileList: [{ required: true, message: '请上传图片', validator: validateFileList }],
-// }
+// 文件类型
+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,
+}
+
+// SSE连接
 
 
 defineOptions({
 defineOptions({
   name: 'Print',
   name: 'Print',
 })
 })
 
 
-function beforeUpload({ files, resolve }) {
-  // 当前允许格式
-  const allowType = ['txt', 'pdf', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'pptx', 'gif', 'png', 'jpg', 'jpeg', 'webp']
-  // 当前允许大小: 10M
-  const maxSize = 10 * 1024 * 1024
-  if (files.length && files[0].path) {
-    const lastIndex = files[0].path.lastIndexOf('.')
-    if (lastIndex != -1) {
-      const fileType = files[0].path.substring(lastIndex + 1)
-      if (allowType.includes(fileType)) {
-        if (files[0].size <= maxSize) {
-          hasFile.value = true
-          resolve(true)
-        } else {
-          toast.warning("上传文件最大为10M")
-          resolve(false)
+// 获取打印机
+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) {
+  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
         }
         }
-      } else {
-        toast.warning(`仅支持 ${allowType.join(",")} 格式的文件`)
-        resolve(false)
       }
       }
-    } else {
-      toast.warning(`仅支持 ${allowType.join(",")} 格式的文件`)
-      resolve(false)
-    }
+    })
+    .catch((e) => {
+    })
+    .finally(() => {})
+}
+
+// 获取文件格式
+function getFileType(fileName) {
+  let type = ""
+  const lastIndex = fileName.lastIndexOf('.')
+  if (lastIndex != -1) {
+    type = fileName.substring(lastIndex + 1)
   }
   }
+  return type
 }
 }
 
 
-function handleFileChange(data) {
-  fileList.value = data.fileList
-  hasFile.value = fileList.value.length > 0
+// 获取文件图标
+function getFileIcon(fileName) {
+  return srcMap[getFileType(fileName)] || otherSrc
 }
 }
 
 
-function handleUpload(file, data, options) {
+// 处理选择文件
+function selectFile() {
+  switch(accept.value) {
+    case "msg":
+      uni.chooseMessageFile({
+        count: 1,
+        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: 1,
+        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 => {
+              if (item.size > maxSize) {
+                failList.push(item)
+              } else {
+                fileList.value.push({
+                  // name: item.path.substring(item.path.lastIndexOf("/") + 1),
+                  name: `图片-${new Date().getTime()}.${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)
+
+                    // 直接下会有白名单问题
+                    // fileList.value.push({
+                    //   name: `微信发票-${item.payee}.pdf`,
+                    //   filePath: item.userInfo.pdfUrl
+                    // })
+                  });
+                }
+              })
+            }
+          }
+        },
+        fail () {
+          toast.warning("选择微信发票失败, 请重试")
+        }
+      })
+      break;
+  }
+}
+
+// 下载文件(用于处理发票)
+function downloadFile(fileName, fileUrl) {
+  console.log('fileUrl: ', fileUrl);
+  uni.downloadFile({
+    url: fileUrl,
+    header: {
+      'Content-Type': "application/png"
+    },
+    success: (res) => {
+      console.log('download res: ', res);
+      // res.tempFilePath
+      fileList.value.push({
+        name: fileName,
+        filePath: res.tempFilePath.replace(".bin", ".png")
+      })
+    },
+    fail () {
+      toast.warning("下载文件失败, 请重试")
+    }
+  })
+}
+
+// 预览文件 (区分文件&图片)
+function previewFile(filePath) {
+  uni.openDocument({
+    filePath: filePath,
+    success () {},
+    fail () {
+      toast.warning("打开文件失败, 请重试")
+    }
+  })
+
+  // uni.downloadFile({
+  //   url: filePath,
+  //   success: (res) => {
+  //     console.log('previewFile res: ', res.tempFilePath);
+  //     uni.openDocument({
+  //       filePath: res.tempFilePath,
+  //       success () {},
+  //       fail () {
+  //         toast.warning("打开文件失败, 请重试")
+  //       }
+  //     })
+  //   },
+  //   fail () {
+  //     toast.warning("下载文件失败, 请重试")
+  //   }
+  // })
+
+  // 图片预览
+  // let imgUrl = props.url || props.url
+  // uni.previewImage({
+  //   current: 0,
+  //   urls: [imgUrl],
+  // })
+}
+
+// 移除文件
+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()
+    })
+  }
+}
+
+// 打印文件 (单个打印任务)
+function handlePrint(filePath) {
   // 构造提交数据
   // 构造提交数据
   let params = {
   let params = {
     id: formData.value.userHubId,
     id: formData.value.userHubId,
@@ -215,18 +453,7 @@ function handleUpload(file, data, options) {
         params[option.key] = formData.value[option.key]
         params[option.key] = formData.value[option.key]
     }
     }
   })
   })
-  // 远程打印
-  // let fileData = new FormData()
-  // fileData.appendFile("file", file.url);
-  // printFile(params, fileData.getData().buffer, fileData.getData().contentType)
-  //   .then((res: any) => {
-  //     if (res.code === 0 && res.body) {
-  //       console.log('res.body: ', res.body);
-  //     }
-  //   })
-  //   .catch((e) => {})
-
-  let queryParams = Object.entries(params).map(([key, value]) => {
+  const queryParams = Object.entries(params).map(([key, value]) => {
     return `${key}=${encodeURIComponent(value)}`;
     return `${key}=${encodeURIComponent(value)}`;
   }).join('&');
   }).join('&');
   
   
@@ -235,37 +462,31 @@ function handleUpload(file, data, options) {
   });
   });
   uni.uploadFile({
   uni.uploadFile({
     url: `${baseUrl}/sys/wx/userHub/print?${queryParams}`,
     url: `${baseUrl}/sys/wx/userHub/print?${queryParams}`,
-    filePath: file.url,
+    filePath: filePath,
     name: 'file', // 这里根据后端需要的字段来定义
     name: 'file', // 这里根据后端需要的字段来定义
-    fileType: options.fileType,
-    formData, // 如果需要额外的 formData
     success: (res: any) => {
     success: (res: any) => {
       if (res.statusCode === 200 && JSON.parse(res.data).code === 0) {
       if (res.statusCode === 200 && JSON.parse(res.data).code === 0) {
         message.confirm({
         message.confirm({
           msg: "打印成功, 是否继续打印?",
           msg: "打印成功, 是否继续打印?",
           closeOnClickModal: false,
           closeOnClickModal: false,
         }).then(() => {
         }).then(() => {
-          hasFile.value = false
           fileList.value = []
           fileList.value = []
           printerOptions.value = []
           printerOptions.value = []
           formData.value = {
           formData.value = {
             printer: ""
             printer: ""
           }
           }
+          if (accept.value == "all") {
+            redirectToUpload()
+          }
         }).catch((error) => {
         }).catch((error) => {
-          uni.reLaunch({
-            url: `/pages/index/index`,
-          })
+          reLaunchToHome()
         })
         })
       } else {
       } else {
-        toast.error('上传打印失败, 请重试')
-        hasFile.value = false
-        fileList.value = []
+        toast.warning('上传打印失败, 请重试')
       }
       }
     },
     },
     fail: (error) => {
     fail: (error) => {
       toast.error('上传打印失败, 请重试')
       toast.error('上传打印失败, 请重试')
-      hasFile.value = false
-      fileList.value = []
     },
     },
     complete: () => {
     complete: () => {
       uni.hideLoading()
       uni.hideLoading()
@@ -273,78 +494,58 @@ function handleUpload(file, data, options) {
   })
   })
 }
 }
 
 
-function validateFileList(value: any) {
-  if (isArray(value) && value.length) {
-    return Promise.resolve()
-  } else {
-    return Promise.reject()
-  }
-}
-
+// 处理提交打印 (支持批量)
 function handleSubmit() {
 function handleSubmit() {
-  if (!hasFile.value) {
+  if (fileList.value.length == 0) {
     toast.warning('请先上传文件')
     toast.warning('请先上传文件')
     return
     return
   }
   }
-  form.value
-    .validate()
-    .then(({ valid, errors }) => {
-      if (valid) {
-        uploader.value.submit()
-      } else {
-        console.log('errors: ', errors);
-      }
-    })
-}
-
-function getPrinterList() {
-  let params = { id: "" }
-  getUserHubPrints(params)
-    .then((res: any) => {
-      if (res.code === 0 && res.body) {
-        printerList.value = res.body || []
-      }
-    })
-    .catch((e) => {})
+  form.value.validate().then(({ valid }) => {
+    if (valid) {
+      // TODO: 批量打印
+      handlePrint(fileList.value[0].filePath)
+    }
+  })
 }
 }
 
 
-function handlePrinterChange(item: any) {
-  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 => {
-            // _formData[option.key] = option.def
-            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(() => {})
+// 处理系统文件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()
+    }
+  })
 }
 }
 
 
 onLoad((option) => {
 onLoad((option) => {
-  if (option && option.accept) accept.value = option.accept
+  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()
   getPrinterList()
 })
 })
 </script>
 </script>
@@ -364,60 +565,45 @@ onLoad((option) => {
   }
   }
 }
 }
 
 
-.inline-txt {
-  display: inline-block;
-  font-size: 14px;
-  margin: 0 8px;
-  color: rgba(0, 0, 0, 0.45);
-  vertical-align: middle;
-}
-
-:deep(.wd-radio-group) {
-  padding: 0 !important;
-  .wd-radio {
-    padding-top: 0 !important;
-  }
-}
 :deep(.wd-upload__mask) {
 :deep(.wd-upload__mask) {
   display: none !important;
   display: none !important;
 }
 }
+
 :deep(.img-btn .wd-img__image) {
 :deep(.img-btn .wd-img__image) {
   width: 160rpx;
   width: 160rpx;
   height: 160rpx;
   height: 160rpx;
   border-radius: 40rpx;
   border-radius: 40rpx;
 }
 }
-// :deep(.wd-upload__evoke) {
-//   width: 80rpx;
-//   height: 80rpx;
-//   margin-bottom: 0;
-//   border-radius: 100%;
-// }
-// :deep(.wd-upload) {
-//   display: flex;
-// }
-// :deep(.wd-upload__evoke-num) {
-//   display: none;
-// }
-// :deep(.wd-icon) {
-//   font-size: 50rpx;
-// }
-// :deep(.img-btn .wd-img__image) {
-//   width: 80rpx;
-//   height: 80rpx;
-//   border-radius: 40rpx;
-// }
-// :deep(.wd-upload__preview) {
-//   display: none !important;
-// }
-// :deep(.gray-btn.wd-button) {
-//   width: 80rpx;
-//   height: 80rpx;
-//   background-color: #ddd;
-//   border-radius: 40rpx;
-// }
-// :deep(.form-ipt) {
-//   padding-right: 0 !important;
-//   padding-left: 0 !important;
-// }
+
+.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>
 </style>
 
 

+ 11 - 4
src/pages/print/job.vue

@@ -33,7 +33,7 @@
     </wd-sticky>
     </wd-sticky>
 
 
     <view class="list-contain">
     <view class="list-contain">
-      <view v-if="queryParams.printer" class="printer">{{ `当前打印机: ${queryParams.printer}` }}</view>
+      <view v-if="queryParams.printer" class="printer">{{ `当前打印机: ${queryParams.printer} ${queryParams.asname ? `(${queryParams.asname})` : ''}` }}</view>
 
 
       <template v-if="dataList.length > 0 || loadStatus == 'loading'">
       <template v-if="dataList.length > 0 || loadStatus == 'loading'">
         <view v-for="item of dataList" :key="item.id" class="item-contain" @click="toDetail(item.id)">
         <view v-for="item of dataList" :key="item.id" class="item-contain" @click="toDetail(item.id)">
@@ -52,7 +52,7 @@
               </view>
               </view>
               <view class="sub-text">
               <view class="sub-text">
                 <text>任务状态: </text>
                 <text>任务状态: </text>
-                <text :class="`color-${getLabelColor(item.jobStatus, jobStatus)}`">{{ getLabel(item.jobStatus, jobStatus) }}</text>
+                <text :class="`color-${getLabelColor(item.jobStatus, jobStatus)}`">{{ getLabel(item.jobStatus, jobStatus) + (item.statusMsg ? `(${item.statusMsg})` : "") }}</text>
               </view>
               </view>
               <view class="sub-text">
               <view class="sub-text">
                 {{ `创建时间: ${item.createTime}` }}
                 {{ `创建时间: ${item.createTime}` }}
@@ -105,6 +105,8 @@ const queryParams: any = reactive({
   completed: "all",
   completed: "all",
   title: '',
   title: '',
   printer: "",
   printer: "",
+  hubId: '',
+  asname: '', // 打印助手名称
 })
 })
 const completedList: any = ref([
 const completedList: any = ref([
   { label: '全部', value: 'all' },
   { label: '全部', value: 'all' },
@@ -202,6 +204,7 @@ function getDataList() {
   }
   }
   if (params.completed === '' || params.completed === 'all') delete params.completed
   if (params.completed === '' || params.completed === 'all') delete params.completed
   if (!params.printer) delete params.printer
   if (!params.printer) delete params.printer
+  if (!params.hubId) delete params.hubId
   getUserHubJobsPage(params)
   getUserHubJobsPage(params)
     .then((res: any) => {
     .then((res: any) => {
       if (res.code === 0 && res.body) {
       if (res.code === 0 && res.body) {
@@ -320,8 +323,12 @@ function resetDataList() {
 
 
 // 页面加载
 // 页面加载
 onLoad((option) => {
 onLoad((option) => {
-  // 指定打印机
-  if (option && option.printer) queryParams.printer = option.printer
+  // 指定打印机 & 助手ID
+  if (option && option.printer && option.hubId) {
+    queryParams.printer = option.printer
+    queryParams.hubId = option.hubId
+    queryParams.asname = option.asname
+  }
   getDataList()
   getDataList()
   // 监听刷新
   // 监听刷新
   uni.$on('refreshData', () => {
   uni.$on('refreshData', () => {

+ 19 - 8
src/pages/printer/index.vue

@@ -29,7 +29,7 @@
 
 
     <view class="list-contain">
     <view class="list-contain">
       <template v-if="dataList.length > 0 || loadStatus == 'loading'">
       <template v-if="dataList.length > 0 || loadStatus == 'loading'">
-        <view v-for="item of dataList" :key="item.id" class="list-box" @click="toDetail('view', item.id)">
+        <view v-for="item of dataList" :key="item.id" class="list-box">
           <view class="name">
           <view class="name">
             <view class="main-text">
             <view class="main-text">
               <view>{{ item.name }}</view>
               <view>{{ item.name }}</view>
@@ -41,7 +41,8 @@
               {{ `品牌型号: ${item['printer-make-and-model']}` }}
               {{ `品牌型号: ${item['printer-make-and-model']}` }}
             </view>
             </view>
             <view class="sub-text">
             <view class="sub-text">
-              {{ `状态: ${getLabel(item['printer-state'], printerStatus)}` }}
+              <text>{{ `状态: ${getLabel(item['printer-state'], printerStatus)} ` }}</text>
+              <text v-if="item['printer-state-message']" class="text-italic">{{ item['printer-state-message'] }}</text>
             </view>
             </view>
           </view>
           </view>
           <view class="opt" @click.stop="showActions(item)">
           <view class="opt" @click.stop="showActions(item)">
@@ -71,6 +72,7 @@ import { getUserHubPrints } from '@/service/api'
 import { getLabel } from '@/utils'
 import { getLabel } from '@/utils'
 import { debounce } from 'wot-design-uni/components/common/util'
 import { debounce } from 'wot-design-uni/components/common/util'
 
 
+const asname = ref("")
 const message = useMessage()
 const message = useMessage()
 const toast = useToast()
 const toast = useToast()
 const total = ref(0)
 const total = ref(0)
@@ -84,7 +86,13 @@ const typeList: any = ref([
   { label: '内网发现', value: 'intranet' }
   { label: '内网发现', value: 'intranet' }
 ])
 ])
 const statusList: any = ref([{ label: '停用', value: '0' }, { label: '正常', value: '1' }])
 const statusList: any = ref([{ label: '停用', value: '0' }, { label: '正常', value: '1' }])
-const printerStatus: any = ref([{ label: '空闲', value: '3' }, { label: '打印中', value: '4' }, { label: '停止', value: '5' }, { label: '已停用', value: '1287' }, { label: '忙碌', value: '1290' }])
+const printerStatus: any = ref([
+  { label: '空闲', value: '3' },
+  { label: '打印中', value: '4' },
+  { label: '停止', value: '5' },
+  { label: '已停用', value: '1287' },
+  { label: '忙碌', value: '1290' }
+])
 const dataList = ref([])
 const dataList = ref([])
 const currentData: any = ref({})
 const currentData: any = ref({})
 // 加载中: loading; 没有数据: finished; 错误: error;
 // 加载中: loading; 没有数据: finished; 错误: error;
@@ -133,9 +141,9 @@ const onSearch = debounce(() => {
 }, 500)
 }, 500)
 
 
 // 查看打印任务
 // 查看打印任务
-function toJob(name?: string) {
+function toJob(data?: any) {
   uni.navigateTo({
   uni.navigateTo({
-    url: `/pages/print/job?printer=${name}`,
+    url: `/pages/print/job?printer=${data.name}&hubId=${data.userHubId}&asname=${data.userHubName || asname.value}`,
   })
   })
 }
 }
 
 
@@ -149,7 +157,7 @@ function showActions(data: any) {
 function selectActions(data: any) {
 function selectActions(data: any) {
   switch (data.item.name) {
   switch (data.item.name) {
     case '查看打印任务':
     case '查看打印任务':
-      toJob(currentData.value.name)
+      toJob(currentData.value)
       break
       break
   }
   }
 }
 }
@@ -165,8 +173,11 @@ function resetDataList() {
 
 
 // 页面加载
 // 页面加载
 onLoad((option) => {
 onLoad((option) => {
-  queryParams.id = option.id || ""
-  if (option && option.id) getDataList()
+  if (option && option.asname) asname.value = option.asname || ""
+  if (option && option.id) {
+    queryParams.id = option.id || ""
+    getDataList()
+  }
 })
 })
 
 
 // 页面显示
 // 页面显示

+ 183 - 0
src/pages/test/index.vue

@@ -0,0 +1,183 @@
+
+<!-- 使用 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>
+          <!-- uni-file-picker -->
+          <wd-button @click="getFile">系统文件</wd-button>
+        </wd-cell>
+
+        <wd-cell title="文件"
+                 title-width="100px"
+                 required>
+          <!-- uni-file-picker -->
+          <wd-button @click="getFromMsg">聊天窗口</wd-button>
+        </wd-cell>
+
+        <wd-cell title="文件"
+                 title-width="100px"
+                 required>
+          <!-- uni-file-picker -->
+          <wd-button @click="getFromImage">相册&相机</wd-button>
+        </wd-cell>
+
+        <wd-cell title="文件"
+                 title-width="100px"
+                 required>
+          <!-- uni-file-picker -->
+          <wd-button @click="openWebView">webview</wd-button>
+        </wd-cell>
+
+        <wd-cell title="发票"
+                 title-width="100px"
+                 required>
+          <!-- uni-file-picker -->
+          <wd-button @click="getInvoice">测试</wd-button>
+        </wd-cell>
+      </wd-cell-group>
+    </wd-form>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { useToast, useMessage } from 'wot-design-uni'
+
+const formData = ref({})
+
+// uniclound 插件市场
+// https://ext.dcloud.net.cn/plugin?id=12245
+// https://ext.dcloud.net.cn/plugin?id=5459
+// https://ext.dcloud.net.cn/plugin?id=12526
+
+// 从本地选择文件  uni.chooseFile(OBJECT) (小程序不支持, 仅uniapp h5页面)
+// https://uniapp.dcloud.net.cn/api/media/file.html
+function getFile() {
+  uni.chooseFile({
+    count: 1,
+    type: "all",
+    success (res) {
+      console.log('uni.chooseFile  res: ', res);
+      // tempFilePath可以作为img标签的src属性显示图片
+      const tempFilePaths = res.tempFiles
+    },
+    fail (err) {
+      console.log('uni.chooseFile err: ', err);
+    }
+  })
+}
+
+/**
+ * 聊天窗口 可指定参数
+ * https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.chooseMessageFile.html
+ * 
+ * 	count	最多可以选择的文件个数,可以 0~100
+ * 
+ *  type  所选的文件的类型	
+ *    all	从所有文件选择
+ *    video	只能选择视频文件
+ *    image	只能选择图片文件
+ *    file	可以选择除了图片和视频之外的其它的文件
+ * 
+ *  extension 根据文件拓展名过滤,仅 type==file 时有效。每一项都不能是空字符串。默认不过滤。
+ *  
+ *  success
+ *  fail
+ *  complete
+ */
+function getFromMsg() {
+  uni.chooseMessageFile({
+    count: 1,
+    type: "all",
+    success (res) {
+      console.log('chooseMessageFile res: ', res);
+      // tempFilePath可以作为img标签的src属性显示图片
+      const tempFilePaths = res.tempFiles
+    }
+  })
+}
+
+/**
+ * 从本地相册选择图片或使用相机拍照。
+ * https://uniapp.dcloud.net.cn/api/media/image.html#chooseimage
+ * https://uniapp.dcloud.net.cn/api/media/video.html#choosemedia
+ * 
+ * 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。
+ * 
+ * count	最多可以选择的文件个数,可以 0~100
+ * 
+ * sourceType  数组  ['album', 'camera'] album 从相册选图,camera 使用相机,默认二者都有。如需直接开相机或直接选相册,请只使用一个选项
+ * 
+ * sizeType  数组  ['original', 'compressed']	 original 原图,compressed 压缩图,默认二者都有
+ * 
+ * 【chooseimage】 extension  数组  根据文件拓展名过滤,每一项都不能是空字符串。默认不过滤。
+ * 
+ * 【chooseimage】 crop 对象  图像裁剪参数,设置后 sizeType 失效	
+ * 
+ * 【chooseMedia】 mediaType  数组  ['image', 'video']  文件类型
+ * 
+ * 【chooseMedia】 camera	String	'back'	仅在 sourceType 为 camera 时生效,使用前置或后置摄像头
+ * 
+ * 【chooseMedia】 maxDuration    拍摄视频最长拍摄时间,单位秒。时间范围为 3s 至 30s 之间
+ * 
+ *  success
+ *  fail
+ *  complete
+ */
+function getFromImage() {
+  uni.chooseImage({
+    count: 1,
+    sourceType: ['album', 'camera'],
+    sizeType: ['original', 'compressed'],
+    extension: ['png', 'jpg', 'jpeg', 'gif', 'webp'],
+    success (res) {
+      console.log('chooseImage res: ', res);
+      // tempFilePath可以作为img标签的src属性显示图片
+      const tempFilePaths = res.tempFiles
+    }
+  })
+}
+
+// 通过 webview 选择系统文件
+function openWebView() {
+  // https://blog.csdn.net/qq_60208156/article/details/132578144
+  // https://dandelioncloud.cn/article/details/1613831738646298625
+  const url = "https://service.1ai.ltd/upload-file/index.html"
+  uni.navigateTo({
+    url: `/pages/webview/index?url=${encodeURIComponent(url)}`,
+  })
+}
+
+// 获取发票信息后 再调用接口获取 pdf_url
+function getInvoice() {
+  // https://developers.weixin.qq.com/miniprogram/dev/api/open-api/invoice/wx.chooseInvoice.html
+  // https://developers.weixin.qq.com/doc/offiaccount/WeChat_Invoice/E_Invoice/Reimburser_API_List.html#5
+  uni.chooseInvoice({
+    success (res) {
+      console.log('chooseInvoice res: ', res);
+      // invoiceInfo	用户选中的发票信息,格式为一个 JSON 字符串,包含三个字段: 
+      //    card_id:所选发票卡券的 cardId,
+      //    encrypt_code:所选发票卡券的加密 code,报销方可以通过 cardId 和 encryptCode 获得报销发票的信息,
+      //    app_id: 发票方的 appId
+    }
+  })
+}
+
+onLoad(() => {
+  
+})
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 15 - 4
src/pages/webview/index.vue

@@ -2,21 +2,32 @@
 {
 {
   layout: 'default',
   layout: 'default',
   style: {
   style: {
-    navigationBarTitleText: 'webView',
+    navigationBarTitleText: '上传文件',
   },
   },
 }
 }
 </route>
 </route>
 
 
 <template>
 <template>
   <view class="">
   <view class="">
-    <web-view :src="url"></web-view>
+    <web-view :src="url" @message="handleMessage"></web-view>
   </view>
   </view>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-const url = "http://192.168.9.104:8181/tos/#/"
+const url = ref("")
+
+function handleMessage(data) {
+  // 判断是否有文件上传
+  uni.setStorageSync('fileList', JSON.parse(data.detail.data))
+  uni.navigateTo({
+    url: `/pages/print/index?accept=all`,
+  })
+}
+
+onLoad((option) => {
+  if (option && option.url) url.value = decodeURIComponent(option.url)
+})
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
-//
 </style>
 </style>

+ 18 - 0
src/service/api/index.ts

@@ -128,6 +128,24 @@ export const printFile = (params, data, contentType) => {
   })
   })
 }
 }
 
 
+/** 获取微信发票信息 */
+export const getInvoiceInfo = (data) => {
+  return http<any>({
+    url: `/api/wx/mini/invoice-info`,
+    method: 'POST',
+    query: data
+  })
+}
+
+/** 批量获取微信发票信息 */
+export const getInvoiceBatch = (data) => {
+  return http<any>({
+    url: `/api/wx/mini/invoice-batch`,
+    method: 'POST',
+    query: data
+  })
+}
+
 // ---------------------- 打印任务相关 ----------------------
 // ---------------------- 打印任务相关 ----------------------
 
 
 /** 查看任务列表 */
 /** 查看任务列表 */

BIN
src/static/images/deleteIcon.png


+ 4 - 0
src/style/index.scss

@@ -205,4 +205,8 @@ page {
 }
 }
 .color-info {
 .color-info {
   color: var(--wot-color-info)
   color: var(--wot-color-info)
+}
+
+.text-italic {
+  font-style: italic;
 }
 }

+ 1 - 0
src/types/uni-pages.d.ts

@@ -14,6 +14,7 @@ interface NavigateToOptions {
        "/pages/print/job" |
        "/pages/print/job" |
        "/pages/print/jobDetail" |
        "/pages/print/jobDetail" |
        "/pages/printer/index" |
        "/pages/printer/index" |
+       "/pages/test/index" |
        "/pages/webview/index";
        "/pages/webview/index";
 }
 }
 interface RedirectToOptions extends NavigateToOptions {}
 interface RedirectToOptions extends NavigateToOptions {}

+ 15 - 0
src/utils/index.ts

@@ -238,4 +238,19 @@ export function toLogin(duration = 0) {
       url: `/pages/login/index?isLogout=1`,
       url: `/pages/login/index?isLogout=1`,
     })
     })
   }, duration)
   }, duration)
+}
+
+// 重定向到上传系统文件
+export function redirectToUpload() {
+  const url = "https://service.1ai.ltd/upload-file/index.html"
+  uni.redirectTo({
+    url: `/pages/webview/index?url=${encodeURIComponent(url)}`,
+  })
+}
+
+// 重定向到首页
+export function reLaunchToHome() {
+  uni.reLaunch({
+    url: `/pages/index/index`,
+  })
 }
 }