Ver código fonte

优化批量打印

“shengjie.huang” 1 ano atrás
pai
commit
8584f49e8b

+ 1 - 0
env/.env

@@ -7,6 +7,7 @@ VITE_WX_APPID = 'wx46467fc3f5573a68'
 # h5部署网站的base,配置到 manifest.config.ts 里的 h5.router.base
 VITE_APP_PUBLIC_BASE = /printer-h5/
 
+VITE_SOCKET_BASEURL = 'wss://service.1ai.ltd/maoer-api'
 VITE_SERVER_BASEURL = 'https://service.1ai.ltd/maoer-api'
 VITE_UPLOAD_BASEURL = 'https://ukw0y1.laf.run/upload'
 

+ 0 - 1
package.json

@@ -102,7 +102,6 @@
     "@dcloudio/uni-mp-xhs": "3.0.0-4020920240930001",
     "@dcloudio/uni-quickapp-webview": "3.0.0-4020920240930001",
     "dayjs": "1.11.10",
-    "event-source-polyfill": "^1.0.31",
     "pinia": "2.0.36",
     "pinia-plugin-persistedstate": "3.2.1",
     "qs": "6.5.3",

+ 0 - 8
pnpm-lock.yaml

@@ -59,9 +59,6 @@ importers:
       dayjs:
         specifier: 1.11.10
         version: 1.11.10
-      event-source-polyfill:
-        specifier: ^1.0.31
-        version: 1.0.31
       pinia:
         specifier: 2.0.36
         version: 2.0.36(typescript@5.7.2)(vue@3.4.21(typescript@5.7.2))
@@ -3029,9 +3026,6 @@ packages:
     resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
     engines: {node: '>= 0.6'}
 
-  event-source-polyfill@1.0.31:
-    resolution: {integrity: sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==}
-
   eventemitter3@5.0.1:
     resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
 
@@ -9488,8 +9482,6 @@ snapshots:
 
   etag@1.8.1: {}
 
-  event-source-polyfill@1.0.31: {}
-
   eventemitter3@5.0.1: {}
 
   execa@5.1.1:

+ 1 - 1
src/pages.json

@@ -130,7 +130,7 @@
       "type": "page",
       "layout": "default",
       "style": {
-        "navigationBarTitleText": "打印任务"
+        "navigationBarTitleText": "查看打印任务"
       }
     }
   ],

+ 180 - 207
src/pages/print/index.vue

@@ -140,36 +140,17 @@
       </wd-cell-group>
 
       <view class="form-footer">
-        <!-- <wd-button type="success" size="large" block @click="connectSSE()">测试SSE</wd-button> -->
         <wd-button type="primary" size="large" block @click="handleSubmit()">确认打印</wd-button>
       </view>
-    </wd-form>
-
-    <!-- 当前打印任务 及 实时状态 -->
-    <!-- 连接SSE: /print/see/connection -->
+    </wd-form>    
   </view>
 </template>
 
 <script lang="ts" setup>
 import { useToast, useMessage } from 'wot-design-uni'
-import { getUserHubPrints, getUserHubAttr, getInvoiceInfo, getInvoiceBatch } from '@/service/api'
+import { getUserHubPrints, getUserHubAttr, getInvoiceBatch } from '@/service/api'
 import { getEnvBaseUrl, redirectToUpload, reLaunchToHome } from '@/utils'
 import { useUserStore } from '@/store'
-// 基于小程序的 uni.request 方式改造 --- 可行
-// 基于 renderjs 仅支持APP和h5 
-// import { uniEventSource } from '@/utils/eventSourceRequest.js'
-
-// 开源项目js文件 给不支持该对象的浏览器提供兼容 v1.0.31
-// 编译报错 "EventSourcePolyfill" is not exported by "src/utils/eventsource.min.js"
-// import { EventSourcePolyfill } from "@/utils/eventsource.js";
-
-// 开源项目的 npm包管理 eventsource-polyfill  v0.9.6
-// 报错 Cannot read property 'XDomainRequest' of undefined
-// import { EventSourcePolyfill } from 'eventsource-polyfill';
-
-// event-source-polyfill  v1.0.31
-// 小程序环境缺少 xmlhttprequest 
-// import { EventSourcePolyfill } from 'event-source-polyfill';
 
 const userStore = useUserStore()
 const token = userStore.token
@@ -214,10 +195,10 @@ const srcMap = {
   'pdf': pdfSrc,
 }
 
-// SSE连接
-const finishList = ref([])
-const info = ref({})
-const eventSource: any = ref({})
+// 上传成功的任务
+const successPrint = ref([])
+// 上传失败的任务
+const failedFiles = ref([])
 
 defineOptions({
   name: 'Print',
@@ -240,6 +221,10 @@ function getPrinterList() {
 
 // 处理更换打印机
 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,
@@ -327,13 +312,13 @@ function selectFile() {
           console.log('chooseImage res: ', res);
           if (res.errMsg == "chooseImage:ok") {
             let failList = []
-            res.tempFiles.forEach(item => {
+            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()}.${getFileType(item.path)}`,
+                  name: `图片-${new Date().getTime() + index}.${getFileType(item.path)}`,
                   filePath: item.path,
                 })
               }
@@ -420,7 +405,7 @@ function removeFile(index) {
   fileList.value.splice(index, 1)
   if (accept.value == "all" && fileList.value.length == 0) {
     message.confirm({
-      msg: "文件已全部移除, 是否重新上传?",
+      msg: "文件已全部移除, 是否重新选择?",
       closeOnClickModal: false,
     }).then(() => {
       redirectToUpload()
@@ -430,75 +415,145 @@ function removeFile(index) {
   }
 }
 
-// 打印文件 (单个打印任务)
-function handlePrint(filePath) {
-  // 构造提交数据
-  let params = {
-    id: formData.value.userHubId,
-    printer: formData.value.printerName,
+// 批量打印文件
+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);
   }
-  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]
+}
+
+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]); // 记录重新上传失败的文件
     }
-  })
-  const queryParams = Object.entries(params).map(([key, value]) => {
-    return `${key}=${encodeURIComponent(value)}`;
-  }).join('&');
-  
-  uni.showLoading({
-    title: '上传中...'
-  });
-  uni.uploadFile({
-    url: `${baseUrl}/sys/wx/userHub/print?${queryParams}`,
-    filePath: filePath,
-    name: 'file', // 这里根据后端需要的字段来定义
-    success: (res: any) => {
-      if (fileList.value.length == 1) {
-        // 单个打印完成
+  }
+
+  // 检查是否还有失败的文件
+  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) {
-          message.confirm({
-            msg: "打印成功, 是否继续打印?",
-            closeOnClickModal: false,
-          }).then(() => {
-            fileList.value = []
-            printerOptions.value = []
-            formData.value = {
-              printer: ""
-            }
-            if (accept.value == "all") {
-              redirectToUpload()
-            }
-          }).catch((error) => {
-            reLaunchToHome()
-          })
+          resolve(res.data);
         } else {
-          console.log("打印异常 res", res);
-          toast.warning('上传打印异常, 请重试')
+          reject(new Error(`上传失败,状态码: ${res.statusCode}`));
         }
-      } else {
-        // 批量打印 完成一个
-        finishList.value.push({
-          filePath,
-        })
+      },
+      fail: (err) => {
+        reject(err);
+      },
+    });
+
+    // 监听上传进度
+    uploadTask.onProgressUpdate((res) => {
+      if (onProgress) {
+        onProgress(res.progress); // 回调上传进度
       }
-    },
-    fail: (error) => {
-      console.log('打印失败 error: ', error);
-      toast.error('上传打印失败, 请重试')
-    },
-    complete: () => {
-      uni.hideLoading()
-    }
-  })
+    });
+  });
 }
 
-// 处理提交打印 (支持批量)
+// 处理提交打印
 function handleSubmit() {
   if (fileList.value.length == 0) {
     toast.warning('请先上传文件')
@@ -506,32 +561,34 @@ function handleSubmit() {
   }
   form.value.validate().then(({ valid }) => {
     if (valid) {
-      // TODO: 批量打印提示
-      finishList.value = []
-      if (fileList.value.length == 1) {
-        handlePrint(fileList.value[0].filePath)
-      } else {
-        message.confirm({
-          // msg: `本次打印文件 ${fileList.value.length} 个, ${fileList.value.length == finishList.value.length ? '已全部上传' : `正在上传第 ${finishList.value.length + 1} 个, 是否继续打印?`}`,
-          msg: `本次打印文件 ${fileList.value.length} 个, 是否继续打印?`,
-          closeOnClickModal: false,
-        }).then(() => {
-          fileList.value = []
-          printerOptions.value = []
-          formData.value = {
-            printer: ""
-          }
-          if (accept.value == "all") {
-            redirectToUpload()
-          }
-        }).catch((error) => {
-          reLaunchToHome()
-        })
+      // 不区分单个/批量, 以批量打印处理
+      handleBatchPrint()
+    }
+  })
+}
 
-        fileList.value.forEach(item => {
-          handlePrint(item.filePath)
-        })
-      }
+// 打印完成后提示词
+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()
     }
   })
 }
@@ -555,99 +612,15 @@ function base64ToTempFilePath(fileName, base64Data, success, fail) {
   })
 }
 
-// 连接SSE
-function connectSSE() {
-  // 基于库 import { EventSourcePolyfill } from 'event-source-polyfill';
-  // 报错 小程序没有 xmlhttprequest
-  // const url = `https://service.1ai.ltd/maoer-api/print/see/connection?token=${token}`
-  // const eventSource = new EventSourcePolyfill(url);
-
-  // eventSource.onopen = (event) =>{
-  //   console.log("连接成功", event);
-  // };
-  // // 监听自定义事件 'update'
-  // eventSource.addEventListener('update', (event) => {
-  //   console.log('Update event:', event.data);
-  // });
-
-  // // 监听默认的 'message' 事件
-  // eventSource.onmessage = (event) => {
-  //   console.log('Message event:', event.data);
-  // };
-
-  // // 监听错误事件
-  // eventSource.onerror = (error) => {
-  //   console.error('EventSource encountered an error:', error);
-  // };
-
-
-
-  // 基于 .js / min.js 中的 EventSourcePolyfill
-  // 仅支持APP + H5 ?
-  // let url = `https://service.1ai.ltd/maoer-api/print/see/connection?token=${token}`
-  // eventSource.value = new EventSourcePolyfill(url, {
-  //   headers: {
-  //     "X-ACCESS-TOKEN": token,
-  //     "Content-Type": "text/event-stream;charset=UTF-8",
-  //     "Cache-Control": "no-cache",
-  //     Connection: "keep-alive",
-  //   },
-  //   //重连时间间隔,单位:毫秒,默认45000毫秒,这里设置为10分钟
-  //   heartbeatTimeout: 10 * 60 * 1000,
-  // });
-
-  // eventSource.value.onopen = () => {
-  //   console.log("建立连接");
-  // };
-  // eventSource.value.onmessage = (e) => {
-  //   console.log("接收到消息:", e);
-  // };
-  // eventSource.value.onerror = (e) => {
-  //   if (e.readyState == EventSource.CLOSED) {
-  //     console.log("连接关闭:", e);
-  //   } else if (eventSource.value.readyState == EventSource.CONNECTING) {
-  //     console.log("正在重连:", e);
-  //   } else {
-  //     console.log("其他异常:", e);
-  //   }
-  // };
-  
-
-  // 基于 renderjs 兼容性仅支持APP(vue) H5
-  // const eventSource = new EventSource(`https://service.1ai.ltd/maoer-api/print/see/connection?token=${token}`); // 确保这是你的服务器地址和端口
-  // eventSource.onmessage = (event) => {
-  //   const data = JSON.parse(event.data);
-  //   console.log('data: ', data);
-  //   // msgList.push(data); // 将接收到的消息添加到数据数组中
-  // };
-  // eventSource.onerror = (error) => {
-  //   console.error('EventSource failed:', error);
-  //   eventSource.close(); // 关闭连接并重新尝试连接等逻辑可以放在这里
-  // };
-
-  // 基于 uni.request
-  // let data = ""
-  // let format = ""
-  // let eventSource = uniEventSource({
-  //   url: `https://service.1ai.ltd/maoer-api/print/see/connection?token=${token}`,
-  //   data: {
-  //     data: data,
-  //     format: format
-  //   },
-  //   onopen: () => {
-  //     console.log("SERVER OPEN")
-  //   },
-  //   onmessage: (res) => {
-  //     console.log(res, "onMessage")
-  //   },
-  //   onerror: (err) => {
-  //     console.error(err)
-  //   },
-  //   onclose: () => {
-  //     console.log("SERVER CLOSE")
-  //   }
-  // })
-  // console.log("EVENTSOURCE::", eventSource)
+// 打开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) => {

+ 137 - 5
src/pages/print/job.vue

@@ -36,7 +36,7 @@
       <view v-if="queryParams.printer" class="printer">{{ `当前打印机: ${queryParams.printer} ${queryParams.asname ? `(${queryParams.asname})` : ''}` }}</view>
 
       <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)">
           <view class="item-info">
             <view class="image">
               <wd-img
@@ -95,7 +95,13 @@ import { useMessage, useToast } from 'wot-design-uni'
 import { getUserHubJobsPage, reprintUserHubJobs, cancelUserHubJobs, deleteUserHubJobs } from '@/service/api'
 import { getLabel, getLabelColor } from '@/utils'
 import { debounce } from 'wot-design-uni/components/common/util'
+import { useUserStore } from '@/store'
+import { handleError } from 'vue'
+import { toLoginWait } from '@/utils'
 
+let socketBaseUrl = import.meta.env.VITE_SOCKET_BASEURL
+const userStore = useUserStore()
+const token = userStore.token
 const message = useMessage()
 const toast = useToast()
 const total = ref(0)
@@ -141,6 +147,8 @@ const pptSrc = '/static/images/ppt.png'
 const pdfSrc = '/static/images/pdf.png'
 const txtSrc = '/static/images/txt.png'
 const otherSrc = '/static/images/other.png'
+// websocket连接对象
+const socketTask = ref(null)
 
 defineOptions({
   name: 'Job',
@@ -228,9 +236,10 @@ const onSearch = debounce(() => {
 }, 500)
 
 // 查看详情
-function toDetail(id: string) {
+function toDetail(item: any) {
+  if (['0'].includes(item.jobStatus)) return
   uni.navigateTo({
-    url: `/pages/print/jobDetail?id=${id}`,
+    url: `/pages/print/jobDetail?id=${item.id}`,
   })
 }
 
@@ -268,7 +277,7 @@ function handleDelete(item) {
       msg: `是否删除打印任务: ${item.title} ?`,
     })
     .then(() => {
-      deleteUserHubJobs(item.id)
+      deleteUserHubJobs({id: item.id})
         .then((res: any) => {
           if (res.code === 0) {
             toast.success('删除成功')
@@ -297,7 +306,7 @@ function showActions(data: any) {
 function selectActions(data: any) {
   switch (data.item.name) {
     case '查看':
-      toDetail(currentData.value.id)
+      toDetail(currentData.value)
       break
     case '重打':
       handleReprint(currentData.value.id)
@@ -323,12 +332,16 @@ function resetDataList() {
 
 // 页面加载
 onLoad((option) => {
+  initWebSocket()
   // 指定打印机 & 助手ID
   if (option && option.printer && option.hubId) {
     queryParams.printer = option.printer
     queryParams.hubId = option.hubId
     queryParams.asname = option.asname
   }
+  if (option && option.tab) {
+    queryParams.completed = option.tab
+  }
   getDataList()
   // 监听刷新
   uni.$on('refreshData', () => {
@@ -337,6 +350,10 @@ onLoad((option) => {
   })
 })
 
+onUnload(() => {
+  closeWebSocket()
+})
+
 // 页面显示
 onShow(() => {
   if (isToTop.value) {
@@ -357,6 +374,118 @@ onReachBottom(() => {
     loadStatus.value = 'finished'
   }
 })
+
+// 创建websocket连接
+function initWebSocket() {
+  socketTask.value = uni.connectSocket({
+    // url: `${socketBaseUrl}/print/ws`,
+    url: `wss://service.1ai.ltd/maoer-api/print/ws`,
+    header: {
+      'token': token
+    },
+    success: () => {
+      console.log("连接成功");
+    },
+    fail: (err) => {
+      console.log("连接失败: ", err);
+    }
+  })
+
+  // 监听 WebSocket 连接打开事件
+  socketTask.value.onOpen(() => {
+    console.log('WebSocket 连接已打开');
+
+    // 发送数据到服务器(心跳)
+    // socketTask.send({
+    //   data: JSON.stringify({ message: 'Hello, Server!' }),
+    //   success: () => {
+    //     console.log('消息发送成功');
+    //   },
+    //   fail: (err) => {
+    //     console.error('消息发送失败', err);
+    //   }
+    // });
+  })
+
+  // 监听 WebSocket 接收到消息事件
+  socketTask.value.onMessage((res) => {
+    console.log('Received message:', res.data);
+
+    // 处理接收到的数据
+    const lines = res.data.split(/\r?\n|\r/).filter(line => line.trim() !== '');
+    const message = lines.reduce((obj, item) => {
+      const [key, value] = item.split(/:(.*)/);
+      obj[key] = key == "data" ? JSON.parse(value) : value;
+      return obj;
+    }, {});
+    console.log('解析后的消息:', message);
+
+    handleWebsocketMessage(message)
+  })
+
+  // 监听 WebSocket 错误事件
+  socketTask.value.onError((err) => {
+    console.error('WebSocket 发生错误', err);
+  });
+
+  // 监听 WebSocket 连接关闭事件
+  socketTask.value.onClose(() => {
+    console.log('WebSocket 连接已关闭');
+    // 重连机制
+    setTimeout(() => {
+      initWebSocket()
+    }, 3000)
+  });
+}
+
+// 断开websocket连接 
+function closeWebSocket() {
+  if (socketTask.value) {
+    socketTask.value.close({
+      success: () => {
+        console.log('WebSocket 连接已关闭');
+      },
+      fail: (err) => {
+        console.error('关闭 WebSocket 连接失败', err);
+      },
+    });
+  }
+}
+
+// 处理Websocket消息
+function handleWebsocketMessage(message: any) {
+  switch (message.event) {
+    case "job-update":
+      handleUpdate(message.data)
+      break;
+    case "not-verify":
+      toast.warning('token失效, 请重新登录')
+      toLoginWait(1500)
+      break;
+  }
+}
+
+// 处理更新数据
+function handleUpdate(data) {
+  if (data.id && dataList.value.findIndex(i => i.id == data.id) !== -1) {
+    // 更新状态
+    let target: any = dataList.value.find(i => i.id == data.id)
+    target.jobStatus = String(data.jobStatus)
+    target.statusMsg = data.statusMsg
+    // 通知
+    switch (String(data.jobStatus)) {
+      case "9":
+        toast.success(`打印完成: ${data.title}`)
+        break;
+      case "6":
+        toast.error(`打印错误: ${data.title}`)
+        break;
+      default:
+        toast.info(`打印信息: ${data.title} ${getLabel(data.jobStatus, jobStatus.value)}`)
+        break; 
+    }
+  }
+}
 </script>
 
 <style lang="scss" scoped>
@@ -461,4 +590,7 @@ onReachBottom(() => {
 :deep(.search-menu .wd-drop-menu__item-title::after) {
   content: none;
 }
+:deep(.wd-message-box__content) {
+  word-break: break-all;
+}
 </style>

+ 21 - 27
src/pages/test/index.vue

@@ -15,37 +15,32 @@
         <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 title="SSE"
+                 title-width="100px"
+                 required>
+          <wd-button @click="openWebViewSSE">webview</wd-button>
+        </wd-cell>
       </wd-cell-group>
     </wd-form>
   </view>
@@ -53,30 +48,19 @@
 
 <script lang="ts" setup>
 import { useToast, useMessage } from 'wot-design-uni'
+import { useUserStore } from '@/store'
 
+const userStore = useUserStore()
 const formData = ref({})
 
-// uniclound 插件市场
+// 从本地选择文件 -- 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页面)
+// 从本地选择文件 -- 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);
-    }
-  })
-}
+
 
 /**
  * 聊天窗口 可指定参数
@@ -174,6 +158,16 @@ function getInvoice() {
   })
 }
 
+// 通过 webview 创建sse
+function openWebViewSSE() {
+  const token = userStore.token
+  const list = JSON.stringify([])
+  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(() => {
   
 })

+ 89 - 4
src/pages/webview/sse.vue

@@ -2,22 +2,107 @@
 {
   layout: 'default',
   style: {
-    navigationBarTitleText: '打印任务',
+    navigationBarTitleText: '查看打印任务',
   },
 }
 </route>
 
 <template>
   <view class="">
-    <web-view :src="url"></web-view>
+    <web-view :src="webviewUrl" ref="webviewRef" id="myWebView"></web-view>
   </view>
 </template>
 
 <script lang="ts" setup>
-const url = ref("")
+const webviewUrl = ref("")
+const webviewRef = ref()
+const webViewContext = ref(null);
+
+// 双向通信 fail、--- 小程序环境evalJS不支持
+// URL参数 fail、---不能实时
+// WebSocket 、--- 需要服务端搭建
+// 全局事件 --- 待尝试
+
+
+// 方法1 (fail): 创建webviewContent
+// 这个uni.createWebviewContext 是 uni-app-x 的api .在uni-app中不能用
+
+// 方法1 (fail): 创建webviewContent 使用 evalJs 通信 (原生APP可以使用 evalJS, 小程序不行)
+// const webViewContext = uni.createWebviewContext('sse')
+// webViewContext.evalJS(`handleAppMsg('这是来自小程序的消息')`)
+
+// function sendToWebview() {
+//   let data = "这是来自App原生的消息"
+//   webviewRef.value.evalJS(`handleAppMsg(${data})`)
+// }
+
+// 方法2: 创建websocket与webview进行通信
+// 在打印列表页创建websocket 与 webview--连接
+// function connectSocket() {
+//   const socket: any = uni.connectSocket({
+//     url: 'wss://service.1ai.ltd',
+//     success: () => {
+//       console.log("连接成功111");
+//     },
+//     fail: (err) => {
+//       console.log("连接失败222");
+//     }
+//   })
+//   console.log('socket: ', socket);
+//   socket.onMessage((res) => {
+//     console.log('Received message:', res.data);
+//   });
+
+//   setInterval(() => {
+//     console.log("send");
+//     socket.send({
+//       data: JSON.stringify({ type: 'notify', content: 'Hello from Mini Program!' })
+//     })
+//   }, 5000)
+// }
+
+// 方法3: postmessage --- createWebViewContext 该方法不存在
+// function postMessage() {
+//   uni.createWebViewContext('myWebView').then(context => {
+//     webViewContext.value = context;
+//     console.log('WebView 上下文获取成功', webViewContext.value);
+//     // 现在你可以使用 context 对象来操作 WebView 了
+//     // 例如:context.evaluateJavaScript('document.title')
+//   }).catch(err => {
+//     console.error('获取 WebView 上下文失败', err);
+//   });
+
+//   setInterval(() => {
+//     // webviewRef.value.postMessage({ data: 'Hello from Mini Program!' })
+//     console.log('webviewRef: ', webviewRef.value);
+//   }, 3000)
+// }
+
+
+
 
 onLoad((option) => {
-  if (option && option.url) url.value = decodeURIComponent(option.url) + "?t=" + new Date().getTime()
+  if (option && option.url) webviewUrl.value = decodeURIComponent(option.url) + "#s=111"
+
+  // uni.$on('webviewEvent', (data) => {
+  //   console.log('收到 WebView 事件:', data);
+  // });
+
+  // setTimeout(() => {
+  //   console.log("小程序发送全局事件 miniProgramEvent");
+  //   uni.$emit('miniProgramEvent', { data: '来自小程序的数据' });
+  // }, 3000)
+
+  // uni.setStorageSync("toWebView", '初始值')
+
+  setInterval(() => {
+    // console.log('uni.getStorageSync("toWebView"): ', uni.getStorageSync("toWebView"));
+    // console.log('uni.getStorageSync("toApp"): ', uni.getStorageSync("toApp"));
+    
+    let s = new Date().getTime()
+    console.log("s:", s);
+    webviewUrl.value = decodeURIComponent(option.url) + "#s=" + s
+  }, 3000)
 })
 </script>
 

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 1048
src/utils/eventsource.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 6
src/utils/eventsource.min.js