Browse Source

图片上传组件

pengjing 11 months ago
parent
commit
f284e58f06

+ 43 - 0
h5app/package-lock.json

@@ -23,6 +23,7 @@
         "ion-multi-picker": "^2.1.3",
         "ionicons": "^6.0.3",
         "pinia": "~2.0.18",
+        "v-viewer": "^3.0.13",
         "vue": "^3.2.47",
         "vue-baidu-map-3x": "^1.0.31",
         "vue-qrcode-reader": "^5.5.3",
@@ -11380,6 +11381,11 @@
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
       "dev": true
     },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
     "node_modules/lodash.debounce": {
       "version": "4.0.8",
       "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
@@ -15182,6 +15188,18 @@
         "uuid": "dist/bin/uuid"
       }
     },
+    "node_modules/v-viewer": {
+      "version": "3.0.13",
+      "resolved": "https://registry.npmmirror.com/v-viewer/-/v-viewer-3.0.13.tgz",
+      "integrity": "sha512-T8pgGzlF0ZCHVpD/32OKsD8MlpI6tqYP3n1XLcSjvGQMc0ABn8nJ4AumxvzAKVQrLRWtDTG6qRGAyCPCmi7ceA==",
+      "dependencies": {
+        "lodash-es": "^4.17.21"
+      },
+      "peerDependencies": {
+        "viewerjs": "^1.11.0",
+        "vue": "^3.0.0"
+      }
+    },
     "node_modules/v8-to-istanbul": {
       "version": "8.1.1",
       "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==",
@@ -15233,6 +15251,12 @@
         "extsprintf": "^1.2.0"
       }
     },
+    "node_modules/viewerjs": {
+      "version": "1.11.6",
+      "resolved": "https://registry.npmmirror.com/viewerjs/-/viewerjs-1.11.6.tgz",
+      "integrity": "sha512-TlhdSp2oEOLFXvEp4psKaeTjR5zBjTRcM/sHUN8PkV1UWuY8HKC8n7GaVdW5Xqnwdr/F1OmzLik1QwDjI4w/nw==",
+      "peer": true
+    },
     "node_modules/vue": {
       "version": "3.2.47",
       "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==",
@@ -25031,6 +25055,11 @@
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
       "dev": true
     },
+    "lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
     "lodash.debounce": {
       "version": "4.0.8",
       "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
@@ -27915,6 +27944,14 @@
       "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
       "dev": true
     },
+    "v-viewer": {
+      "version": "3.0.13",
+      "resolved": "https://registry.npmmirror.com/v-viewer/-/v-viewer-3.0.13.tgz",
+      "integrity": "sha512-T8pgGzlF0ZCHVpD/32OKsD8MlpI6tqYP3n1XLcSjvGQMc0ABn8nJ4AumxvzAKVQrLRWtDTG6qRGAyCPCmi7ceA==",
+      "requires": {
+        "lodash-es": "^4.17.21"
+      }
+    },
     "v8-to-istanbul": {
       "version": "8.1.1",
       "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==",
@@ -27956,6 +27993,12 @@
         "extsprintf": "^1.2.0"
       }
     },
+    "viewerjs": {
+      "version": "1.11.6",
+      "resolved": "https://registry.npmmirror.com/viewerjs/-/viewerjs-1.11.6.tgz",
+      "integrity": "sha512-TlhdSp2oEOLFXvEp4psKaeTjR5zBjTRcM/sHUN8PkV1UWuY8HKC8n7GaVdW5Xqnwdr/F1OmzLik1QwDjI4w/nw==",
+      "peer": true
+    },
     "vue": {
       "version": "3.2.47",
       "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==",

+ 1 - 0
h5app/package.json

@@ -26,6 +26,7 @@
     "ion-multi-picker": "^2.1.3",
     "ionicons": "^6.0.3",
     "pinia": "~2.0.18",
+    "v-viewer": "^3.0.13",
     "vue": "^3.2.47",
     "vue-baidu-map-3x": "^1.0.31",
     "vue-qrcode-reader": "^5.5.3",

+ 14 - 0
h5app/src/api/system/file.ts

@@ -35,6 +35,7 @@ export function uploadBase64(data: any) {
         },
         {
             isNew: true,
+            errorMsg: '上次失败!'
         },
     );
 }
@@ -61,3 +62,16 @@ export function downloadPdm(url: any) {
     })
 }
 
+export function getFileBase64(fileId: string) {
+    return request<object>(
+        {
+            url: 'system/file/getFileBase64',
+            method: 'get',
+            params: {fileId:fileId},
+        },
+        {
+            isNew: true,
+        },
+    );
+}
+

+ 214 - 0
h5app/src/components/bImage.vue

@@ -0,0 +1,214 @@
+<template>
+  <div class="img-list" v-viewer>
+    <div class="img-item" v-for="(it,key) in imageList" :key="key">
+      <div v-if="it.blobUrl">
+        <img :src="it.blobUrl"/>
+        <ion-icon v-if="!readonly" :icon="closeCircleOutline" color="danger" class="remove-icon"
+                  @click="deleteFile(it)"></ion-icon>
+      </div>
+    </div>
+    <div class="img-item" v-if="(!isSingle || imageList.length==0) && !readonly">
+      <ion-button color="light" @click="takePicture()">
+        <ion-icon :icon="addOutline" size="large"></ion-icon>
+      </ion-button>
+    </div>
+    <ion-item v-if="readonly && imageList.length==0">
+      <ion-label style="flex: 0 0 100% !important;"><p>暂无</p></ion-label>
+    </ion-item>
+  </div>
+</template>
+<script lang="ts">
+import {defineComponent, ref} from "vue";
+import {camera, cameraOutline, addOutline, closeCircleOutline} from "ionicons/icons";
+import {alertController, IonIcon, IonThumbnail, loadingController} from '@ionic/vue';
+import {Camera, CameraResultType, CameraSource} from '@capacitor/camera';
+import {deleteFile as deleteFileApi, getFileBase64, getList, uploadBase64} from '@/api/system/file';
+import {presentAlert} from "@/api/common";
+import {dealImage, base64ToBlob} from "@/utils/imageUtils";
+
+export default defineComponent({
+      name: 'b-image',
+      components: {IonIcon},
+      props: {
+        fileRefId: {type: String, default: ''},
+        fileType: {type: Number, default: 1},
+        readonly: {type: Boolean, default: true},
+        isSingle: {type: Boolean, default: false} //只能上次一张图片
+      },
+      setup(props) {
+        const accept = 'png,jpeg,jpg,gif';
+        const imageList = ref<any>([]);
+
+        const getImageList = () => {
+          imageList.value = [];
+          if (props.fileRefId)
+            getList({fileRefID: props.fileRefId}).then(resultList => {
+              imageList.value = resultList as any;
+              imageList.value.forEach((img: any) => {
+                if (!img.base64) {
+                  getFileBase64(img.fileId).then(base64Str => {
+                    if (base64Str)
+                      img.blobUrl = URL.createObjectURL(base64ToBlob("data:image/png;base64," + base64Str));
+                  });
+                }
+              });
+            });
+        };
+
+        const takePicture = async () => {
+          if (props.fileRefId === '' || props.fileRefId == null) {
+            await presentAlert("参数fileRefId为空");
+            return false;
+          }
+
+          const image = await Camera.getPhoto({
+            quality: 90,
+            allowEditing: true,
+            source: CameraSource.Photos,
+            resultType: CameraResultType.Uri,
+          });
+
+          if (!accept?.split(',').includes(image.format)) {
+            await presentAlert("只能上传格式为:" + accept + "的文件");
+            return null;
+          }
+
+          dealImage(image.webPath as string, 1000, saveFile);
+        };
+
+        const saveFile = async (newBase64: string) => {
+          const formData = new FormData();
+          formData.append('base64Str', newBase64 as any);
+          formData.append("fileRefId", props.fileRefId);
+          formData.append("fileType", props.fileType.toString());
+          formData.append("isSingle", props.isSingle ? "1" : "0");
+
+          const loading = await loadingController.create({
+            cssClass: 'my-custom-class',
+            message: '正在上传,请稍等...',
+            duration: 2000,
+          });
+
+          await loading.present();
+
+          uploadBase64(formData).then((result) => {
+            if (result) {
+              presentAlert("上传成功");
+              /*getImageList();*/
+              getList({fileRefID: props.fileRefId}).then(resultList => {
+                const imgList = resultList as any;
+                imgList.forEach((img: any) => {
+                  if (imageList.value.filter((it: any) => it.fileId === img.fileId).length == 0) {
+                    getFileBase64(img.fileId).then(base64Str => {
+                      if (base64Str) {
+                        img.blobUrl = URL.createObjectURL(base64ToBlob("data:image/png;base64," + base64Str));
+                        imageList.value.push(img);
+                      }
+
+                    });
+                  }
+                });
+              });
+            }
+          })
+        };
+
+        const deleteFile = async (item: any) => {
+          const alert = await alertController.create({
+            header: '提示!',
+            message: '是否确认删除?',
+            buttons: [
+              {
+                text: '取消',
+                role: 'cancel'
+              },
+              {
+                text: '确认删除',
+                handler: async () => {
+                  const loading = await loadingController.create({
+                    cssClass: 'my-custom-class',
+                    message: '正在删除,请稍等...',
+                    duration: 2000,
+                  });
+
+                  await loading.present();
+
+                  deleteFileApi({fileId: item.fileId}).then(result => {
+                    if (result) {
+                      imageList.value = imageList.value.filter((it: any) => it.fileId != item.fileId);
+                    }
+                  });
+                }
+              }
+            ],
+          });
+
+          await alert.present();
+        }
+
+        getImageList();
+
+        return {
+          camera,
+          IonThumbnail,
+          takePicture,
+          imageList,
+          deleteFile,
+          addOutline,
+          closeCircleOutline
+        }
+      }
+    }
+);
+</script>
+<style lang="less">
+.img-list {
+  display: flex;
+  text-align: center;
+  flex-direction: row;
+  flex-wrap: wrap;
+  padding: 5px 10px;
+
+  .img-item {
+    width: 25%;
+    position: relative;
+    padding: 0 5px;
+
+    img {
+      height: 73px;
+      object-fit: cover;
+      border-radius: 10px;
+      width: 100%;
+    }
+
+    .remove-icon {
+      position: absolute;
+      right: -3px;
+      top: -6px;
+      font-size: 20px;
+    }
+
+    ion-button {
+      --box-shadow: 0px;
+      height: 73px;
+      width: 75px;
+    }
+  }
+}
+
+.showbigimg {
+  z-index: 10;
+  width: 100%;
+  height: 100%;
+  background: #e1e1e1;
+  display: flex;
+  justify-content: center; /*在主轴上的对齐*/
+  align-items: center; /*在交叉轴上中心点的对齐*/
+
+  img {
+    opacity: 1;
+    vertical-align: middle;
+  }
+}
+
+</style>

+ 4 - 0
h5app/src/main.ts

@@ -27,10 +27,14 @@ import '@ionic/vue/css/display.css';
 /* Theme variables */
 import './theme/variables.css';
 
+import Viewer from "v-viewer";
+import 'viewerjs/dist/viewer.css'
+
 const app = createApp(App);
 app.config.globalProperties.$routeActive=false; //退出登录清除缓存
 app.use(IonicVue)
 app.use(router);
+app.use(Viewer);
 
 setupStore(app);
 

+ 29 - 13
h5app/src/utils/imageUtils.ts

@@ -2,7 +2,7 @@
  * @param {file}
  * @returns {string} 转Base64文件
  */
-export function getBase64(file:any) {
+export function getBase64(file: any) {
     return new Promise((resolve, reject) => {
         ///FileReader类就是专门⽤来读⽂件的
         const reader = new FileReader();
@@ -15,34 +15,35 @@ export function getBase64(file:any) {
         reader.onerror = (error) => reject(error);
     });
 }
+
 /**
  * @param {base64url}
  * @returns {string} // 获取文件得大小
  */
-export function calSize(base64url:string) {
+export function calSize(base64url: string) {
     let str = base64url.replace('data:image/png;base64,', '');
     const equalIndex = str.indexOf('=');
-    if(str.indexOf('=') > 0) {
+    if (str.indexOf('=') > 0) {
         str = str.substring(0, equalIndex);
     }
     const strLength = str.length;
     const fileLength = strLength - (strLength / 8) * 2;
     // 返回单位为MB的大小
-    return  parseFloat((fileLength / (1024 * 1024)).toFixed(2));
+    return parseFloat((fileLength / (1024 * 1024)).toFixed(2));
 }
 
 /**
  * @param {path,w,callback}
  * @returns {callback} // 通过canvas压缩base64图片 并压缩
  */
-export function dealImage(path:string, w=1000, callback:any){
+export function dealImage(path: string, w = 1000, callback: any) {
     const newImage = new Image();
-    const size=calSize(path);//获取mb大小
+    const size = calSize(path);//获取mb大小
     let quality = 0.52
-    if(size<=1){//1 mb
-        quality=0.9;
+    if (size <= 1) {//1 mb
+        quality = 0.9;
     }
-    if(size>1){
+    if (size > 1) {
         quality = 0.8
     }
 
@@ -50,7 +51,7 @@ export function dealImage(path:string, w=1000, callback:any){
     newImage.setAttribute("crossOrigin", 'Anonymous');    // url为外域时需要
     let imgWidth;
     let imgHeight;
-    newImage.onload = function() {
+    newImage.onload = function () {
         imgWidth = newImage.width;
         imgHeight = newImage.height;
         const canvas = document.createElement("canvas");
@@ -83,13 +84,28 @@ export function dealImage(path:string, w=1000, callback:any){
  * @method calSize 获取图片大小
  */
 const other = {
-    base64: (file:any) => {
+    base64: (file: any) => {
         return getBase64(file)
     },
-    getDealImage:(path:any, w:number, callback:any)=>{
+    getDealImage: (path: any, w: number, callback: any) => {
         return dealImage(path, w, callback)
     },
-    getCalSize:(url:string)=>{
+    getCalSize: (url: string) => {
         return calSize(url)
     }
+}
+
+export function base64ToBlob(data: string) {
+    const arr = data.split(',');
+    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+    // @ts-ignore
+    const mime = arr[0].match(/:(.*?);/)[1];
+    const bstr = atob(arr[1]);
+    let n = bstr.length;
+    const u8arr = new Uint8Array(n);
+
+    while (n--) {
+        u8arr[n] = bstr.charCodeAt(n);
+    }
+    return new Blob([u8arr], {type: mime});
 }

+ 4 - 1
h5app/src/views/pages/demo/edit.vue

@@ -91,6 +91,8 @@
 <!--       <div>
          <b-qr-scan></b-qr-scan>
        </div>-->
+
+       <b-image :file-ref-id="'ssssss'" :readonly="false" :is-single="false"></b-image>
      </ion-content>
 
    </ion-page>
@@ -98,11 +100,12 @@
 <script>
 import {defineComponent} from "vue";
 import BQrScan from "@/components/bQrScan.vue";
+import BImage from "@/components/bImage.vue";
 
 export default defineComponent({
   name:"demo_edit",
   // eslint-disable-next-line vue/no-unused-components
-  components:{BQrScan},
+  components:{BQrScan,BImage},
   setup(){
     return {}
   }