ソースを参照

优化远程打印

“shengjie.huang” 1 年間 前
コミット
116db0b771

+ 1 - 1
src/interceptors/request.ts

@@ -43,7 +43,7 @@ const httpInterceptor = {
       // TIPS: 如果需要对接多个后端服务,也可以在这里处理,拼接成所需要的地址
     }
     // 1. 请求超时
-    options.timeout = 10000 // 10s
+    options.timeout = 20000 // 20s
     // 2. (可选)添加小程序端请求头标识
     options.header = {
       platform, // 可选,与 uniapp 定义的平台一致,告诉后台来源

+ 14 - 0
src/pages.json

@@ -82,6 +82,20 @@
         "navigationBarTitleText": "我的"
       }
     },
+    {
+      "path": "pages/print/index",
+      "type": "page",
+      "style": {
+        "navigationBarTitleText": "远程打印"
+      }
+    },
+    {
+      "path": "pages/printer/index",
+      "type": "page",
+      "style": {
+        "navigationBarTitleText": "打印机列表"
+      }
+    },
     {
       "path": "pages/webview/index",
       "type": "page",

+ 19 - 3
src/pages/assistant/index.vue

@@ -33,8 +33,8 @@
           <view class="name">
             <view class="main-text">
               <view>{{ item.asname }}</view>
-              <wd-tag v-if="item.enabled == '0'" type="danger" mark custom-class="ml-2">
-                {{ getLabel('0', statusList) }}
+              <wd-tag :type="item.onlineStatus == 'online' ? 'success' : 'danger'" mark custom-class="ml-2">
+                {{ getLabel(item.onlineStatus || 'offline', onlineStatus) }}
               </wd-tag>
             </view>
             <view class="sub-text">
@@ -88,7 +88,7 @@ const typeList: any = ref([
   { label: '扫码添加', value: 'qrcode' },
   { label: '内网发现', value: 'intranet' }
 ])
-const statusList: any = ref([{ label: '停用', value: '0' }, { label: '正常', value: '1' }])
+const onlineStatus: any = ref([{ label: '离线', value: 'offline' }, { label: '在线', value: 'online' }])
 const dataList = ref([])
 const currentData: any = ref({})
 // 加载中: loading; 没有数据: finished; 错误: error;
@@ -108,6 +108,7 @@ function buildCurrentActions() {
   currentActions.value = [
     { name: '查看' },
     { name: '编辑' },
+    { name: '查看打印机' },
     { name: '删除', color: '#fa4350' }
   ]
 }
@@ -174,6 +175,18 @@ function handleDelete() {
     })
 }
 
+// 跳转到打印机列表
+function toPrinter() {
+  uni.navigateTo({
+    url: `/pages/printer/index?id=${currentData.value.id || ''}`,
+  })
+}
+
+// TODO: 根据打印助手的内网ip字段"intranetIp" 检查是否局域网 (采用局域网接口)
+function checkStatus() {
+
+}
+
 function showActions(data: any) {
   actionsTitle.value = data.asname ? `打印助手: ${data.asname}` : '操作'
   currentData.value = data || {}
@@ -189,6 +202,9 @@ function selectActions(data: any) {
     case '编辑':
       toDetail('edit', currentData.value.id)
       break
+    case '查看打印机':
+      toPrinter()
+      break;
     case '删除':
       handleDelete()
       break

+ 40 - 13
src/pages/connect/index.vue

@@ -16,6 +16,7 @@
       <wd-img :src="deviceImg" width="100" mode="widthFix"></wd-img>
       <view class="check-box">
         <view class="check-result">{{ checkResult }}</view>
+        <view v-if="currentWifi" class="check-result">{{ `设备当前连接WiFi: ${currentWifi}` }}</view>
         <wd-button @click="getConnectedWifi()">重新检查</wd-button>
       </view>
       <view v-if="!checkWiFiFlag">{{ `请先连接WiFi:${targetWifi}` }}</view>
@@ -53,7 +54,8 @@
           prop="pwd"
           placeholder="请输入家里的无线密码"
           clearable
-          :rules="[{ required: true, message: '请输入家里的无线密码' }]"
+          show-password
+          :rules="[{ required: true, pattern: /^.{8,}$/, message: '请输入不少于8位无线密码' }]"
         />
       </wd-cell-group>
 
@@ -75,7 +77,9 @@ const targetWifi = "maoer-printer-hub"
 // const targetWifi = "ZBA_1813"
 const checkWiFiFlag = ref(false)
 const checkIPFlag = ref(false)
-// 已连接的wifi信息
+// 设备当前连接的wifi
+const currentWifi = ref("")
+// 手机已连接的wifi信息
 const wifiInfo: any = ref({})
 // 表单信息
 const form = ref()
@@ -89,7 +93,7 @@ const checkResult = computed(() => {
   return checkWiFiFlag.value ? (checkIPFlag.value ? "已检测到设备" : "未检测到设备") : "未连接指定WiFi"
 })
 
-// 检查wifi连接是否正确, 匹配前缀
+// 检查手机wifi连接是否正确, 匹配前缀
 function checkWifi() {
   if (wifiInfo.value.SSID.startsWith(targetWifi)) {
     checkWiFiFlag.value = true
@@ -98,7 +102,7 @@ function checkWifi() {
 }
 
 // 获取已连接中的 Wi-Fi 信息。
-function getConnectedWifi() {
+function getConnectedWifi(isFirst = false) {
   uni.startWifi().then(() => {
     setTimeout(() => {
       uni.getConnectedWifi({
@@ -111,12 +115,12 @@ function getConnectedWifi() {
           //   signalStrength: 99
           // }
           wifiInfo.value = res.wifi || {}
-          console.log('res.wifi: ', res.wifi);
+          console.log('WiFi res: ', res.wifi);
           checkWifi()
-          if (checkWiFiFlag.value) checkIP()
+          if (checkWiFiFlag.value) checkIP(isFirst)
         },
         fail: (err) => {
-          console.log('err: ', err);
+          console.log('WiFi err: ', err);
           toast.error('获取WiFi信息失败')
         }
       })
@@ -125,23 +129,36 @@ function getConnectedWifi() {
 }
 
 // 局域网通信: 检查IP地址是否请求成功
-function checkIP() {
+function checkIP(isFirst: boolean) {
+  console.log("checkIP");
   getWifiStatus().then((res: any) => {
     console.log('当前连接WiFi: ', res);
+    currentWifi.value = res.body || ""
     checkIPFlag.value = true
-    getWifiList()
+    if (isFirst) getWifiList()
+  }).catch((err)=> {})
+}
+
+// 局域网通信: 获取设备当前连接wifi
+function getDeviceWifiStatus() {
+  getWifiStatus().then((res: any) => {
+    currentWifi.value = res.body || ""
   }).catch((err)=> {})
 }
 
 // 局域网通信: 获取可连接wifi列表
 function getWifiList() {
   loading.value = true
+  uni.showLoading({
+    title: '加载无线列表中...'
+  });
   getWifis().then((res: any) => {
     console.log('wifi列表: ', res.body);
-    wifiList.value = res.body || []
+    wifiList.value = (res.body || []).filter((i: any) => i.ssid)
   }).catch((err)=> {})
   .finally(() => {
     loading.value = false
+    uni.hideLoading()
   })
 }
 
@@ -150,18 +167,28 @@ function handleSubmit() {
   form.value.validate().then(({ valid, errors }) => {
     if (valid) {
       loading.value = true
+      uni.showLoading({
+        title: '正在提交...'
+      });
       connectWifi(formData.value).then((res: any) => {
+        console.log('res: ', res);
         toast.success('连接成功')
-      }).catch((err) => {})
+        currentWifi.value = res.body || ""
+      }).catch((err) => {
+        console.log('err: ', err);
+        toast.error('连接失败')
+        getDeviceWifiStatus()
+      })
       .finally(() => {
         loading.value = false
+        uni.hideLoading()
       })
     }
   })
 }
 
 onLoad(() => {
-  getConnectedWifi()
+  getConnectedWifi(true)
 })
 </script>
 
@@ -176,7 +203,7 @@ onLoad(() => {
 .check-box {
   margin: 40rpx 0;
   .check-result {
-    margin-bottom: 20rpx;
+    margin-bottom: 30rpx;
   }
 }
 </style>

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

@@ -33,7 +33,7 @@
           <view class="item-title">打印助手</view>
           <view class="item-desc">我的打印助手</view>
         </view>
-        <view class="guide-item" @click="developing()">
+        <view class="guide-item" @click="toPrint()">
           <wd-img :src="fileImgSrc" width="100" mode="widthFix"></wd-img>
           <view class="item-title">远程打印</view>
           <view class="item-desc">无需驱动 隔空打印</view>
@@ -142,6 +142,17 @@ function toUserHub() {
   }
 }
 
+function toPrint() {
+  if (isLogined.value) {
+    uni.navigateTo({
+      url: `/pages/print/index`,
+    })
+  } else {
+    toast.warning('请先前往登录')
+    toLogin(1500)
+  }
+}
+
 function developing() {
   toast.warning('该功能开发中...')
 }

+ 412 - 0
src/pages/print/index.vue

@@ -0,0 +1,412 @@
+<!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page;推荐使用json5,更强大,且允许注释 -->
+<route lang="json5">
+{
+  style: {
+    navigationBarTitleText: '远程打印',
+  },
+}
+</route>
+
+<template>
+  <view class="page-form">
+    <!-- 选择打印机--获取打印参数--上传打印 -->
+    <wd-form ref="form" :model="formData">
+      <!-- :rules=rules -->
+      <wd-cell-group custom-class="form-group" border>
+        <wd-cell title="上传文件"
+                 title-width="100px"
+                 required
+                 prop="fileList">
+          <!-- :rules="[{ required: true, message: '请上传图片', validator: validateFileList }]" -->
+          <wd-upload
+            ref="uploader"
+            accept="all"
+            :auto-upload="false"
+            :limit="1"
+            :file-list="fileList"
+            :before-upload="beforeUpload"
+            :upload-method="handleUpload"
+            @change="handleFileChange"
+          ></wd-upload>
+        </wd-cell>
+
+        <wd-select-picker
+          v-model="formData.printer"
+          label="打印机"
+          label-width="100px"
+          type="radio"
+          :columns="printerList"
+          value-key="id"
+          label-key="name"
+          :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">
+            <!-- :rules="[{ required: true, message: `请选择${option.text}` }]" -->
+
+            <!-- 滑块 -->
+            <!-- <wd-slider v-model="formData[option.key]"
+                       :min="option.min"
+                       :max="option.max" /> -->
+
+            <!-- 输入框: 从 xx 到 xx (需增加 min max 控制) -->
+            <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, printFile } from '@/service/api'
+import { isArray } from 'wot-design-uni/components/common/util'
+import FormData from '@/utils/formData'
+import { getEnvBaseUrl } from '@/utils'
+
+const baseUrl = getEnvBaseUrl()
+const toast = useToast()
+const message = useMessage()
+const uploader = ref()
+const fileList = ref([])
+const form = ref()
+const formData: any = ref({
+  printer: "",
+})
+const printerList: any = ref([])
+const printerOptions: any = ref([])
+const hasFile = ref(false)
+
+// TODO: 优化上传文件校验
+// const rules = {
+//   fileList: [{ required: true, message: '请上传图片', validator: validateFileList }],
+// }
+
+defineOptions({
+  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)
+        }
+      } else {
+        toast.warning(`仅支持 ${allowType.join(",")} 格式的文件`)
+        resolve(false)
+      }
+    } else {
+      toast.warning(`仅支持 ${allowType.join(",")} 格式的文件`)
+      resolve(false)
+    }
+  }
+}
+
+function handleFileChange(data) {
+  fileList.value = data.fileList
+  hasFile.value = fileList.value.length > 0
+}
+
+function handleUpload(file, data, options) {
+  // 构造提交数据
+  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]
+    }
+  })
+  // 远程打印
+  // 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]) => {
+    return `${key}=${encodeURIComponent(value)}`;
+  }).join('&');
+
+  uni.uploadFile({
+    url: `${baseUrl}/sys/wx/userHub/print?${queryParams}`,
+    filePath: file.url,
+    name: 'file', // 这里根据后端需要的字段来定义
+    fileType: options.fileType,
+    formData, // 如果需要额外的 formData
+    success: async (res: any) => {
+      if (res.code === 0) {
+        message.confirm("打印成功, 是否继续打印?").then(() => {
+          hasFile.value = false
+          fileList.value = []
+          formData.value = {
+            printer: ""
+          }
+        }).catch((error) => {
+          uni.reLaunch({
+            url: `/pages/index/index`,
+          })
+        })
+      } else {
+        toast.error('上传打印失败, 请重试')
+        hasFile.value = false
+        fileList.value = []
+      }
+    },
+    fail: (error) => {
+      toast.error('上传打印失败, 请重试')
+      hasFile.value = false
+      fileList.value = []
+    },
+  })
+}
+
+function validateFileList(value: any) {
+  if (isArray(value) && value.length) {
+    return Promise.resolve()
+  } else {
+    return Promise.reject()
+  }
+}
+
+function handleSubmit() {
+  if (!hasFile.value) {
+    toast.warning('请先上传文件')
+    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) => {})
+}
+
+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) => {
+      // TODO: 测试离线情况 417 设备离线没有属性值
+    })
+    .finally(() => {})
+}
+
+onLoad(() => {
+  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;
+  }
+}
+
+.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) {
+  display: none !important;
+}
+:deep(.img-btn .wd-img__image) {
+  width: 160rpx;
+  height: 160rpx;
+  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;
+// }
+</style>
+

+ 262 - 0
src/pages/printer/index.vue

@@ -0,0 +1,262 @@
+<!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page;推荐使用json5,更强大,且允许注释 -->
+<route lang="json5">
+{
+  style: {
+    navigationBarTitleText: '打印机列表',
+  },
+}
+</route>
+<template>
+  <view class="page-list">
+    <wd-sticky>
+      <view class="sticky-box">
+        <wd-search
+          placeholder="请输入打印机名称"
+          placeholder-left
+          hide-cancel
+          v-model="queryParams.printer"
+          @change="onSearch"
+          @clear="onSearch"
+        >
+          <!-- <template v-slot:prefix>
+            <wd-drop-menu custom-class="search-menu">
+              <wd-drop-menu-item v-model="queryParams.addType" :options="typeList" @change="onSearch" />
+            </wd-drop-menu>
+          </template> -->
+        </wd-search>
+      </view>
+    </wd-sticky>
+
+    <view class="list-contain">
+      <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 class="name">
+            <view class="main-text">
+              <view>{{ item.name }}</view>
+              <wd-tag :type="item.enable == '0' ? 'danger' : 'success'" mark custom-class="ml-2">
+                {{ getLabel(item.enable, statusList) }}
+              </wd-tag>
+            </view>
+            <view class="sub-text">
+              {{ `品牌型号: ${item['printer-make-and-model']}` }}
+            </view>
+            <view class="sub-text">
+              {{ `状态: ${getLabel(item['printer-state'], printerStatus)}` }}
+            </view>
+          </view>
+          <view class="opt" @click.stop="showActions(item)">
+            <wd-button type="icon" icon="more"></wd-button>
+          </view>
+        </view>
+        <!-- 加载更多 -->
+        <wd-loadmore custom-class="loadmore" :state="loadStatus" />
+      </template>
+      <wd-status-tip v-else image="content" tip="暂无内容" />
+    </view>
+
+    <!-- 动作面板 -->
+    <wd-action-sheet
+      v-model="isShowActions"
+      :actions="currentActions"
+      :title="actionsTitle"
+      @close="isShowActions = false"
+      @select="selectActions"
+    />
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { useMessage, useToast } from 'wot-design-uni'
+import { getUserHubPrints } from '@/service/api'
+import { getLabel } from '@/utils'
+import { debounce } from 'wot-design-uni/components/common/util'
+
+const message = useMessage()
+const toast = useToast()
+const total = ref(0)
+const queryParams: any = reactive({
+  id: "",
+  printer: '',
+})
+const typeList: any = ref([
+  { label: '全部', value: 'all' },
+  { label: '扫码添加', value: 'qrcode' },
+  { label: '内网发现', value: 'intranet' }
+])
+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 dataList = ref([])
+const currentData: any = ref({})
+// 加载中: loading; 没有数据: finished; 错误: error;
+const loadStatus: any = ref('loading')
+// 动作面板
+const isShowActions = ref(false)
+const actionsTitle = ref('')
+const currentActions: any = ref([{ loading: true }])
+const isToTop = ref(false)
+
+defineOptions({
+  name: 'Printer',
+})
+
+// 根据状态 构造操作面板
+function buildCurrentActions() {
+  currentActions.value = [
+    { name: '查看' },
+  ]
+}
+
+function getDataList() {
+  loadStatus.value = 'loading'
+  const params = { ...queryParams }
+  if (params.printer) {
+    params.printer = '*' + params.printer + '*'
+  }
+  if (params.id === '') {
+    delete params.id
+  }
+  getUserHubPrints(params)
+    .then((res: any) => {
+      if (res.code === 0 && res.body) {
+        dataList.value = res.body || []
+        total.value = dataList.value.length
+      }
+    })
+    .catch((e) => {})
+    .finally(() => {
+      loadStatus.value = 'finished'
+    })
+}
+
+const onSearch = debounce(() => {
+  getDataList()
+}, 500)
+
+// 跳转到详情
+function toDetail(id?: string) {
+  // uni.navigateTo({
+  //   url: `/pages/assistant/detail?type=${type}&id=${id || ''}`,
+  // })
+}
+
+function showActions(data: any) {
+  actionsTitle.value = data.name ? `打印机: ${data.name}` : '操作'
+  currentData.value = data || {}
+  buildCurrentActions()
+  isShowActions.value = true
+}
+
+function selectActions(data: any) {
+  switch (data.item.name) {
+    case '查看':
+      toDetail(currentData.value.id)
+      break
+  }
+}
+
+// 查询列表, 滚动到顶部, 返回第一页
+function resetDataList() {
+  uni.pageScrollTo({
+    scrollTop: 0,
+    duration: 300,
+  })
+  onSearch()
+}
+
+// 页面加载
+onLoad((option) => {
+  queryParams.id = option.id || ""
+  if (option && option.id) getDataList()
+})
+
+// 页面显示
+onShow(() => {
+  if (isToTop.value) {
+    uni.pageScrollTo({
+      scrollTop: 0,
+      duration: 300,
+    })
+    isToTop.value = false
+  }
+})
+
+// 滚动到底部
+onReachBottom(() => {
+  // if (dataList.value.length < total.value) {
+  //   queryParams.pageNo++
+  //   getDataList()
+  // } else {
+  //   loadStatus.value = 'finished'
+  // }
+})
+</script>
+
+<style lang="scss" scoped>
+.sticky-box {
+  width: 100vw;
+  height: 100rpx;
+  background-color: #ffffff;
+}
+.search-type {
+  position: relative;
+  height: 60rpx;
+  padding: 0 16rpx 0 32rpx;
+  line-height: 60rpx;
+}
+.search-type::after {
+  position: absolute;
+  top: 10rpx;
+  right: 0;
+  bottom: 10rpx;
+  width: 2rpx;
+  content: '';
+  background: rgba(0, 0, 0, 0.25);
+}
+.search-type {
+  :deep(.icon-arrow) {
+    display: inline-block;
+    font-size: 40rpx;
+    vertical-align: middle;
+  }
+}
+.list-contain {
+  padding: 10rpx 30rpx 30rpx;
+  overflow-y: auto;
+
+  .list-box {
+    display: flex;
+    align-items: center;
+    padding: 30rpx 0 30rpx 30rpx;
+    margin-bottom: 30rpx;
+    border: 1px solid #eee;
+
+    .name {
+      flex: 1;
+      .main-text {
+        display: flex;
+        align-items: center;
+      }
+      .sub-text {
+        margin-top: 20rpx;
+        font-size: 28rpx;
+        color: #00000073;
+      }
+    }
+    .opt {
+      width: 80rpx;
+      text-align: center;
+    }
+  }
+}
+//搜索下拉菜单
+:deep(.search-menu .wd-drop-menu__list) {
+  background: none;
+}
+:deep(.search-menu .wd-drop-menu__item) {
+  height: 62rpx;
+  line-height: 62rpx;
+}
+:deep(.search-menu .wd-drop-menu__item-title::after) {
+  content: none;
+}
+</style>

+ 62 - 4
src/service/api/index.ts

@@ -69,7 +69,7 @@ export const getUserHub = (id) => {
   })
 }
 
-/** 查询打印助手 分页信息 */
+/** 查询打印助手 分页信息 (带有内网IP 可用于检测是否局域网打印) */
 export const getUserHubPage = (data) => {
   return http<any>({
     url: '/sys/wx/userHub/page',
@@ -95,6 +95,64 @@ export const updateUserHub = (data: any) => {
   })
 }
 
+/** 获取打印机列表 */
+export const getUserHubPrints = (data) => {
+  return http<any>({
+    url: `/sys/wx/userHub/prints`,
+    method: 'GET',
+    query: data
+  })
+}
+
+/** 获取打印机属性参数 */
+export const getUserHubAttr = (data) => {
+  return http<any>({
+    url: `/sys/wx/userHub/attr`,
+    method: 'GET',
+    query: data
+  })
+}
+
+/** 查看已经完成任务列表 */
+export const getUserHubJobs = (data) => {
+  return http<any>({
+    url: `/sys/wx/userHub/jobs`,
+    method: 'GET',
+    query: data
+  })
+}
+
+/** 查看未完成任务列表 */
+export const getUserHubJobsNotCompleted = (data) => {
+  return http<any>({
+    url: `/sys/wx/userHub/jobs-not-completed`,
+    method: 'GET',
+    query: data
+  })
+}
+
+/** 取消打印任务 */
+export const getUserHubJobsCancel = (data) => {
+  return http<any>({
+    url: `/sys/wx/userHub/jobs-cancel`,
+    method: 'GET',
+    query: data
+  })
+}
+
+/** 远程打印文件 */
+export const printFile = (params, data, contentType) => {
+  return http<any>({
+    url: "/sys/wx/userHub/print",
+    method: "POST",
+    query: params,
+    data: data,
+    header: {
+      'Content-Type': contentType,
+    },
+  })
+}
+
 // ---------------------- 设备配网相关 需要引导连接WiFi ----------------------
 /** 名称: maoer-printer-hub
  *  密码: 0123456789
@@ -102,21 +160,21 @@ export const updateUserHub = (data: any) => {
 
 export const getWifiStatus = () => {
   return http<any>({
-    url: "http://192.168.9.114:5002/api/wifi-status",
+    url: "http://192.168.90.1:5002/api/wifi-status",
     method: "GET",
   })
 }
 
 export const getWifis = () => {
   return http<any>({
-    url: "http://192.168.9.114:5002/api/wifis",
+    url: "http://192.168.90.1:5002/api/wifis",
     method: "GET",
   })
 }
 
 export const connectWifi = (data: any) => {
   return http<any>({
-    url: "http://192.168.9.114:5002/api/wifi-conntion",
+    url: "http://192.168.90.1:5002/api/wifi-conntion",
     method: "POST",
     data: data,
   })

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

@@ -10,6 +10,8 @@ interface NavigateToOptions {
        "/pages/connect/index" |
        "/pages/login/index" |
        "/pages/personal/index" |
+       "/pages/print/index" |
+       "/pages/printer/index" |
        "/pages/webview/index";
 }
 interface RedirectToOptions extends NavigateToOptions {}

+ 2 - 2
src/typings.d.ts

@@ -3,8 +3,8 @@
 declare global {
   type IResData<T> = {
     code: number
-    msg: string
-    data: T
+    message: string
+    body: T
   }
 
   // uni.uploadFile文件上传参数

+ 145 - 0
src/utils/formData.js

@@ -0,0 +1,145 @@
+import mimeMap from './mimeMap'
+ 
+function FormData(){
+  let fileManager = wx.getFileSystemManager();
+  let data = {};
+  let files = [];
+ 
+  this.append = (name, value)=>{
+    data[name] = value;
+    return true;
+  }
+ 
+  this.appendFile = (name, path)=>{
+    let buffer = fileManager.readFileSync(path);
+    if(Object.prototype.toString.call(buffer).indexOf("ArrayBuffer") < 0){
+      return false;
+    }
+    files.push({
+      name: name,
+      buffer: buffer,
+      fileName: getFileNameFromPath(path)
+    });
+    return true;
+  }
+ 
+  this.getData = ()=>convert(data, files)
+}
+ 
+function getFileNameFromPath(path){
+  let idx=path.lastIndexOf("/");
+  return path.substr(idx+1);
+}
+ 
+function convert(data, files){
+  let boundaryKey = 'wxmpFormBoundary' + randString(); // 数据分割符,一般是随机的字符串
+  let boundary = '--' + boundaryKey;
+  let endBoundary = boundary + '--';
+ 
+  let postArray = [];
+  //拼接参数
+  if(data && Object.prototype.toString.call(data) == "[object Object]"){
+    for(let key in data){
+      postArray = postArray.concat(formDataArray(boundary, key, data[key]));
+    }
+  }
+  //拼接文件
+  if(files && Object.prototype.toString.call(files) == "[object Array]"){
+    for(let i in files){
+      let file = files[i];
+      postArray = postArray.concat(formDataArray(boundary, file.name, file.buffer, file.fileName));
+    }
+  }
+  //结尾
+  let endBoundaryArray = [];
+  for (var i = 0; i < endBoundary.length; i++) { // 最后取出结束boundary的charCode
+    endBoundaryArray.push(...endBoundary.utf8CodeAt(i));
+  }
+  postArray = postArray.concat(endBoundaryArray);
+  return {
+    contentType: 'multipart/form-data; boundary=' + boundaryKey,
+    buffer: new Uint8Array(postArray).buffer
+  }
+}
+ 
+function randString() {
+  let res = "";
+  for (let i = 0; i < 17; i++) {
+    let n = parseInt(Math.random() * 62);
+    if (n <= 9) {
+      res += n;
+    }
+    else if (n <= 35) {
+      res += String.fromCharCode(n + 55);
+    }
+    else {
+      res += String.fromCharCode(n + 61);
+    }
+  }
+  return res;
+}
+ 
+function formDataArray(boundary, name, value, fileName){
+  let dataString = '';
+  let isFile = !!fileName;
+ 
+  dataString += boundary + '\r\n';
+  dataString += 'Content-Disposition: form-data; name="' + name + '"';
+  if (isFile){
+    dataString += '; filename="' + fileName + '"' + '\r\n';
+    dataString += 'Content-Type: ' + getFileMime(fileName) + '\r\n\r\n';
+  }
+  else{
+    dataString += '\r\n\r\n';
+    dataString += value;
+  }
+ 
+  var dataArray = [];
+  for (var i = 0; i < dataString.length; i++) { // 取出文本的charCode(10进制)
+    dataArray.push(...dataString.utf8CodeAt(i));
+  }
+ 
+  if (isFile) {
+    let fileArray = new Uint8Array(value);
+    dataArray = dataArray.concat(Array.prototype.slice.call(fileArray));
+  }
+  dataArray.push(..."\r".utf8CodeAt());
+  dataArray.push(..."\n".utf8CodeAt());
+ 
+  return dataArray;
+}
+ 
+function getFileMime(fileName){
+  let idx = fileName.lastIndexOf(".");
+  let mime = mimeMap[fileName.substr(idx)];
+  return mime?mime:"application/octet-stream"
+}
+ 
+String.prototype.utf8CodeAt = function(i) {
+  var str = this;
+  var out = [], p = 0;
+  var c = str.charCodeAt(i);
+  if (c < 128) {
+    out[p++] = c;
+  } else if (c < 2048) {
+    out[p++] = (c >> 6) | 192;
+    out[p++] = (c & 63) | 128;
+  } else if (
+      ((c & 0xFC00) == 0xD800) && (i + 1) < str.length &&
+      ((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) {
+    // Surrogate Pair
+    c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF);
+    out[p++] = (c >> 18) | 240;
+    out[p++] = ((c >> 12) & 63) | 128;
+    out[p++] = ((c >> 6) & 63) | 128;
+    out[p++] = (c & 63) | 128;
+  } else {
+    out[p++] = (c >> 12) | 224;
+    out[p++] = ((c >> 6) & 63) | 128;
+    out[p++] = (c & 63) | 128;
+  }
+  return out;
+};
+ 
+ 
+export default FormData;

+ 1 - 1
src/utils/http.ts

@@ -27,7 +27,7 @@ export const http = <T>(options: CustomRequestOptions) => {
             uni.showToast({
               icon: 'none',
               duration: 5000,
-              title: (res.data as IResData<T>).msg || '请求错误',
+              title: (res.data as IResData<T>).message || '请求错误',
             })
           reject(res)
         }

+ 345 - 0
src/utils/mimeMap.js

@@ -0,0 +1,345 @@
+const mimeMap = {
+  "0.001": "application/x-001",
+  "0.323": "text/h323",
+  "0.907": "drawing/907",
+  ".acp": "audio/x-mei-aac",
+  ".aif": "audio/aiff",
+  ".aiff": "audio/aiff",
+  ".asa": "text/asa",
+  ".asp": "text/asp",
+  ".au": "audio/basic",
+  ".awf": "application/vnd.adobe.workflow",
+  ".bmp": "application/x-bmp",
+  ".c4t": "application/x-c4t",
+  ".cal": "application/x-cals",
+  ".cdf": "application/x-netcdf",
+  ".cel": "application/x-cel",
+  ".cg4": "application/x-g4",
+  ".cit": "application/x-cit",
+  ".cml": "text/xml",
+  ".cmx": "application/x-cmx",
+  ".crl": "application/pkix-crl",
+  ".csi": "application/x-csi",
+  ".cut": "application/x-cut",
+  ".dbm": "application/x-dbm",
+  ".dcd": "text/xml",
+  ".der": "application/x-x509-ca-cert",
+  ".dib": "application/x-dib",
+  ".doc": "application/msword",
+  ".drw": "application/x-drw",
+  ".dwf": "Model/vnd.dwf",
+  ".dwg": "application/x-dwg",
+  ".dxf": "application/x-dxf",
+  ".emf": "application/x-emf",
+  ".ent": "text/xml",
+  ".eps": "application/x-ps",
+  ".etd": "application/x-ebx",
+  ".fax": "image/fax",
+  ".fif": "application/fractals",
+  ".frm": "application/x-frm",
+  ".gbr": "application/x-gbr",
+  ".gif": "image/gif",
+  ".gp4": "application/x-gp4",
+  ".hmr": "application/x-hmr",
+  ".hpl": "application/x-hpl",
+  ".hrf": "application/x-hrf",
+  ".htc": "text/x-component",
+  ".html": "text/html",
+  ".htx": "text/html",
+  ".ico": "image/x-icon",
+  ".iff": "application/x-iff",
+  ".igs": "application/x-igs",
+  ".img": "application/x-img",
+  ".isp": "application/x-internet-signup",
+  ".java": "java/*",
+  ".jpe": "image/jpeg",
+  ".jpeg": "image/jpeg",
+  ".jpg": "application/x-jpg",
+  ".jsp": "text/html",
+  ".lar": "application/x-laplayer-reg",
+  ".lavs": "audio/x-liquid-secure",
+  ".lmsff": "audio/x-la-lms",
+  ".ltr": "application/x-ltr",
+  ".m2v": "video/x-mpeg",
+  ".m4e": "video/mpeg4",
+  ".man": "application/x-troff-man",
+  ".mdb": "application/msaccess",
+  ".mfp": "application/x-shockwave-flash",
+  ".mhtml": "message/rfc822",
+  ".mid": "audio/mid",
+  ".mil": "application/x-mil",
+  ".mnd": "audio/x-musicnet-download",
+  ".mocha": "application/x-javascript",
+  ".mp1": "audio/mp1",
+  ".mp2v": "video/mpeg",
+  ".mp4": "video/mpeg4",
+  ".mpd": "application/vnd.ms-project",
+  ".mpeg": "video/mpg",
+  ".mpga": "audio/rn-mpeg",
+  ".mps": "video/x-mpeg",
+  ".mpv": "video/mpg",
+  ".mpw": "application/vnd.ms-project",
+  ".mtx": "text/xml",
+  ".net": "image/pnetvue",
+  ".nws": "message/rfc822",
+  ".out": "application/x-out",
+  ".p12": "application/x-pkcs12",
+  ".p7c": "application/pkcs7-mime",
+  ".p7r": "application/x-pkcs7-certreqresp",
+  ".pc5": "application/x-pc5",
+  ".pcl": "application/x-pcl",
+  ".pdf": "application/pdf",
+  ".pdx": "application/vnd.adobe.pdx",
+  ".pgl": "application/x-pgl",
+  ".pko": "application/vnd.ms-pki.pko",
+  ".plg": "text/html",
+  ".plt": "application/x-plt",
+  ".png": "application/x-png",
+  ".ppa": "application/vnd.ms-powerpoint",
+  ".pps": "application/vnd.ms-powerpoint",
+  ".ppt": "application/x-ppt",
+  ".prf": "application/pics-rules",
+  ".prt": "application/x-prt",
+  ".ps": "application/postscript",
+  ".pwz": "application/vnd.ms-powerpoint",
+  ".ra": "audio/vnd.rn-realaudio",
+  ".ras": "application/x-ras",
+  ".rdf": "text/xml",
+  ".red": "application/x-red",
+  ".rjs": "application/vnd.rn-realsystem-rjs",
+  ".rlc": "application/x-rlc",
+  ".rm": "application/vnd.rn-realmedia",
+  ".rmi": "audio/mid",
+  ".rmm": "audio/x-pn-realaudio",
+  ".rms": "application/vnd.rn-realmedia-secure",
+  ".rmx": "application/vnd.rn-realsystem-rmx",
+  ".rp": "image/vnd.rn-realpix",
+  ".rsml": "application/vnd.rn-rsml",
+  ".rtf": "application/msword",
+  ".rv": "video/vnd.rn-realvideo",
+  ".sat": "application/x-sat",
+  ".sdw": "application/x-sdw",
+  ".slb": "application/x-slb",
+  ".slk": "drawing/x-slk",
+  ".smil": "application/smil",
+  ".snd": "audio/basic",
+  ".sor": "text/plain",
+  ".spl": "application/futuresplash",
+  ".ssm": "application/streamingmedia",
+  ".stl": "application/vnd.ms-pki.stl",
+  ".sty": "application/x-sty",
+  ".swf": "application/x-shockwave-flash",
+  ".tg4": "application/x-tg4",
+  ".tif": "image/tiff",
+  ".tiff": "image/tiff",
+  ".top": "drawing/x-top",
+  ".tsd": "text/xml",
+  ".uin": "application/x-icq",
+  ".vcf": "text/x-vcard",
+  ".vdx": "application/vnd.visio",
+  ".vpg": "application/x-vpeg005",
+  ".vsd": "application/x-vsd",
+  ".vst": "application/vnd.visio",
+  ".vsw": "application/vnd.visio",
+  ".vtx": "application/vnd.visio",
+  ".wav": "audio/wav",
+  ".wb1": "application/x-wb1",
+  ".wb3": "application/x-wb3",
+  ".wiz": "application/msword",
+  ".wk4": "application/x-wk4",
+  ".wks": "application/x-wks",
+  ".wma": "audio/x-ms-wma",
+  ".wmf": "application/x-wmf",
+  ".wmv": "video/x-ms-wmv",
+  ".wmz": "application/x-ms-wmz",
+  ".wpd": "application/x-wpd",
+  ".wpl": "application/vnd.ms-wpl",
+  ".wr1": "application/x-wr1",
+  ".wrk": "application/x-wrk",
+  ".ws2": "application/x-ws",
+  ".wsdl": "text/xml",
+  ".xdp": "application/vnd.adobe.xdp",
+  ".xfd": "application/vnd.adobe.xfd",
+  ".xhtml": "text/html",
+  ".xml": "text/xml",
+  ".xq": "text/xml",
+  ".xquery": "text/xml",
+  ".xsl": "text/xml",
+  ".xwd": "application/x-xwd",
+  ".sis": "application/vnd.symbian.install",
+  ".x_t": "application/x-x_t",
+  ".apk": "application/vnd.android.package-archive",
+  "0.301": "application/x-301",
+  "0.906": "application/x-906",
+  ".a11": "application/x-a11",
+  ".ai": "application/postscript",
+  ".aifc": "audio/aiff",
+  ".anv": "application/x-anv",
+  ".asf": "video/x-ms-asf",
+  ".asx": "video/x-ms-asf",
+  ".avi": "video/avi",
+  ".biz": "text/xml",
+  ".bot": "application/x-bot",
+  ".c90": "application/x-c90",
+  ".cat": "application/vnd.ms-pki.seccat",
+  ".cdr": "application/x-cdr",
+  ".cer": "application/x-x509-ca-cert",
+  ".cgm": "application/x-cgm",
+  ".class": "java/*",
+  ".cmp": "application/x-cmp",
+  ".cot": "application/x-cot",
+  ".crt": "application/x-x509-ca-cert",
+  ".css": "text/css",
+  ".dbf": "application/x-dbf",
+  ".dbx": "application/x-dbx",
+  ".dcx": "application/x-dcx",
+  ".dgn": "application/x-dgn",
+  ".dll": "application/x-msdownload",
+  ".dot": "application/msword",
+  ".dtd": "text/xml",
+  ".dwf": "application/x-dwf",
+  ".dxb": "application/x-dxb",
+  ".edn": "application/vnd.adobe.edn",
+  ".eml": "message/rfc822",
+  ".epi": "application/x-epi",
+  ".eps": "application/postscript",
+  ".exe": "application/x-msdownload",
+  ".fdf": "application/vnd.fdf",
+  ".fo": "text/xml",
+  ".g4": "application/x-g4",
+  ".tif": "image/tiff",
+  ".gl2": "application/x-gl2",
+  ".hgl": "application/x-hgl",
+  ".hpg": "application/x-hpgl",
+  ".hqx": "application/mac-binhex40",
+  ".hta": "application/hta",
+  ".htm": "text/html",
+  ".htt": "text/webviewhtml",
+  ".icb": "application/x-icb",
+  ".ico": "application/x-ico",
+  ".ig4": "application/x-g4",
+  ".iii": "application/x-iphone",
+  ".ins": "application/x-internet-signup",
+  ".IVF": "video/x-ivf",
+  ".jfif": "image/jpeg",
+  ".jpe": "application/x-jpe",
+  ".jpg": "image/jpeg",
+  ".js": "application/x-javascript",
+  ".la1": "audio/x-liquid-file",
+  ".latex": "application/x-latex",
+  ".lbm": "application/x-lbm",
+  ".ls": "application/x-javascript",
+  ".m1v": "video/x-mpeg",
+  ".m3u": "audio/mpegurl",
+  ".mac": "application/x-mac",
+  ".math": "text/xml",
+  ".mdb": "application/x-mdb",
+  ".mht": "message/rfc822",
+  ".mi": "application/x-mi",
+  ".midi": "audio/mid",
+  ".mml": "text/xml",
+  ".mns": "audio/x-musicnet-stream",
+  ".movie": "video/x-sgi-movie",
+  ".mp2": "audio/mp2",
+  ".mp3": "audio/mp3",
+  ".mpa": "video/x-mpg",
+  ".mpe": "video/x-mpeg",
+  ".mpg": "video/mpg",
+  ".mpp": "application/vnd.ms-project",
+  ".mpt": "application/vnd.ms-project",
+  ".mpv2": "video/mpeg",
+  ".mpx": "application/vnd.ms-project",
+  ".mxp": "application/x-mmxp",
+  ".nrf": "application/x-nrf",
+  ".odc": "text/x-ms-odc",
+  ".p10": "application/pkcs10",
+  ".p7b": "application/x-pkcs7-certificates",
+  ".p7m": "application/pkcs7-mime",
+  ".p7s": "application/pkcs7-signature",
+  ".pci": "application/x-pci",
+  ".pcx": "application/x-pcx",
+  ".pdf": "application/pdf",
+  ".pfx": "application/x-pkcs12",
+  ".pic": "application/x-pic",
+  ".pl": "application/x-perl",
+  ".pls": "audio/scpls",
+  ".png": "image/png",
+  ".pot": "application/vnd.ms-powerpoint",
+  ".ppm": "application/x-ppm",
+  ".ppt": "application/vnd.ms-powerpoint",
+  ".pr": "application/x-pr",
+  ".prn": "application/x-prn",
+  ".ps": "application/x-ps",
+  ".ptn": "application/x-ptn",
+  ".r3t": "text/vnd.rn-realtext3d",
+  ".ram": "audio/x-pn-realaudio",
+  ".rat": "application/rat-file",
+  ".rec": "application/vnd.rn-recording",
+  ".rgb": "application/x-rgb",
+  ".rjt": "application/vnd.rn-realsystem-rjt",
+  ".rle": "application/x-rle",
+  ".rmf": "application/vnd.adobe.rmf",
+  ".rmj": "application/vnd.rn-realsystem-rmj",
+  ".rmp": "application/vnd.rn-rn_music_package",
+  ".rmvb": "application/vnd.rn-realmedia-vbr",
+  ".rnx": "application/vnd.rn-realplayer",
+  ".rpm": "audio/x-pn-realaudio-plugin",
+  ".rt": "text/vnd.rn-realtext",
+  ".rtf": "application/x-rtf",
+  ".sam": "application/x-sam",
+  ".sdp": "application/sdp",
+  ".sit": "application/x-stuffit",
+  ".sld": "application/x-sld",
+  ".smi": "application/smil",
+  ".smk": "application/x-smk",
+  ".sol": "text/plain",
+  ".spc": "application/x-pkcs7-certificates",
+  ".spp": "text/xml",
+  ".sst": "application/vnd.ms-pki.certstore",
+  ".stm": "text/html",
+  ".svg": "text/xml",
+  ".tdf": "application/x-tdf",
+  ".tga": "application/x-tga",
+  ".tif": "application/x-tif",
+  ".tld": "text/xml",
+  ".torrent": "application/x-bittorrent",
+  ".txt": "text/plain",
+  ".uls": "text/iuls",
+  ".vda": "application/x-vda",
+  ".vml": "text/xml",
+  ".vsd": "application/vnd.visio",
+  ".vss": "application/vnd.visio",
+  ".vst": "application/x-vst",
+  ".vsx": "application/vnd.visio",
+  ".vxml": "text/xml",
+  ".wax": "audio/x-ms-wax",
+  ".wb2": "application/x-wb2",
+  ".wbmp": "image/vnd.wap.wbmp",
+  ".wk3": "application/x-wk3",
+  ".wkq": "application/x-wkq",
+  ".wm": "video/x-ms-wm",
+  ".wmd": "application/x-ms-wmd",
+  ".wml": "text/vnd.wap.wml",
+  ".wmx": "video/x-ms-wmx",
+  ".wp6": "application/x-wp6",
+  ".wpg": "application/x-wpg",
+  ".wq1": "application/x-wq1",
+  ".wri": "application/x-wri",
+  ".ws": "application/x-ws",
+  ".wsc": "text/scriptlet",
+  ".wvx": "video/x-ms-wvx",
+  ".xdr": "text/xml",
+  ".xfdf": "application/vnd.adobe.xfdf",
+  ".xls": "application/vnd.ms-excel",
+  ".xlw": "application/x-xlw",
+  ".xpl": "audio/scpls",
+  ".xql": "text/xml",
+  ".xsd": "text/xml",
+  ".xslt": "text/xml",
+  ".x_b": "application/x-x_b",
+  ".sisx": "application/vnd.symbian.install",
+  ".ipa": "application/vnd.iphone",
+  ".xap": "application/x-silverlight-app",
+  ".zip": "application/x-zip-compressed",
+}
+export default mimeMap;

+ 2 - 1
tsconfig.json

@@ -35,6 +35,7 @@
     "src/**/*.tsx",
     "src/**/*.jsx",
     "src/**/*.vue",
-    "src/**/*.json"
+    "src/**/*.json",
+    "src/utils/formData.js"
   ]
 }