lex hace 1 año
padre
commit
5a52b8546d

+ 11 - 0
src/views/natural-resources/components/my-resources/index.module.less

@@ -232,6 +232,17 @@
   }
 }
 
+.uploadCover {
+  width: 920px;
+
+  :global {
+    .n-card-header {
+      padding-top: 0;
+      padding-bottom: 0;
+    }
+  }
+}
+
 
 .removeVisiable {
   width: 432px;

+ 13 - 0
src/views/natural-resources/components/my-resources/index.tsx

@@ -27,6 +27,7 @@ import resourceChecked from '../../images/resource-checked.png';
 import MyResourcesGuide from '@/custom-plugins/guide-page/myResources-guide';
 import SaveModal from './save-modal';
 import deepClone from '/src/helpers/deep-clone';
+import UploadCover from './upload-cover';
 export default defineComponent({
   name: 'share-resources',
   setup() {
@@ -50,6 +51,7 @@ export default defineComponent({
       tableList: [] as any,
       uploadStatus: false,
       saveStatus: false,
+      uploadCoverStatus: true, // 封面图
       show: false,
       item: {} as any,
       editStatus: false, // 是否编辑
@@ -428,6 +430,17 @@ export default defineComponent({
             }}
           />
         </NModal>
+
+        <NModal
+          v-model:show={state.uploadCoverStatus}
+          preset="card"
+          showIcon={false}
+          class={['modalTitle ', styles.uploadCover]}
+          // title={'上传资源'}
+          blockScroll={false}>
+          <UploadCover />
+        </NModal>
+
         {showGuide.value ? <MyResourcesGuide></MyResourcesGuide> : null}
 
         <NModal

+ 1 - 0
src/views/natural-resources/components/my-resources/save-modal/index.tsx

@@ -226,6 +226,7 @@ export default defineComponent({
           videoElement.play();
           setTimeout(() => {
             // 过500ms 暂停, 解决空白问题
+            videoElement.currentTime = 0;
             videoElement.pause();
             // 创建canvas元素
             const canvas: any = document.createElement('canvas');

+ 101 - 0
src/views/natural-resources/components/my-resources/upload-cover/index.module.less

@@ -0,0 +1,101 @@
+.uploadFile {
+
+  :global {
+    .n-upload-dragger {
+      padding: 0;
+      border: none;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      background: #FFFFFF;
+      border: 1px solid #DCE2F1;
+      border-radius: 20px;
+
+      &:hover {
+        border-color: #198CFE;
+      }
+    }
+  }
+
+  .uploadBtn {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+    width: 433px;
+    height: 275px;
+    font-weight: 600;
+    font-size: max(15px, 13Px);
+    color: #131415;
+
+
+
+    span {
+      padding-top: 13px;
+      display: block;
+      text-align: center;
+      font-weight: 400;
+      font-size: max(13px, 11Px);
+      color: #131415;
+    }
+
+    .iconUploadAdd {
+      width: 50px;
+      height: 50px;
+      margin-bottom: 27px;
+    }
+  }
+}
+
+.uploadHeader {
+  padding: 0 27px;
+  display: flex;
+
+  .headerItem {
+    position: relative;
+    padding: 17px 0 13px;
+    font-weight: 600;
+    font-size: max(18px, 14Px);
+    color: #131415;
+
+    &::after {
+      content: '';
+      display: inline-block;
+      position: absolute;
+      bottom: 0;
+      width: 100%;
+      left: 0;
+      height: 3px;
+      background: #198CFE;
+    }
+  }
+}
+
+.uploadContainer {
+  text-align: center;
+  height: 538px;
+  background-color: #F7F8F9;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  :global {
+    .n-upload {
+      width: auto;
+    }
+  }
+}
+
+.uploadBtnGroup {
+  padding: 17px 27px;
+  display: flex;
+  justify-content: space-between;
+
+  :global {
+    .n-button {
+      border-radius: 8px;
+      font-size: max(15px, 13Px);
+      min-width: 97px;
+    }
+  }
+}

+ 465 - 4
src/views/natural-resources/components/my-resources/upload-cover/index.tsx

@@ -1,9 +1,470 @@
-import { defineComponent } from 'vue';
+import {
+  NButton,
+  NModal,
+  NSpace,
+  NSpin,
+  NUpload,
+  NUploadDragger,
+  UploadCustomRequestOptions,
+  UploadFileInfo,
+  useMessage
+} from 'naive-ui';
+import { defineComponent, watch, PropType, reactive, ref } from 'vue';
+import { policy } from '@/components/upload-file/api';
+import Copper from '@/components/upload-file/copper';
+import axios from 'axios';
 import styles from './index.module.less';
+import iconUploadAdd from '../../../images/icon-upload2.png';
+import { NaturalTypeEnum, PageEnum } from '@/enums/pageEnum';
+import { formatUrlType } from '../upload-modal';
 
+/**
+ * 1. 图片上传可以进行裁剪
+ * 2. 视频上传可以选择某一帧做为封面
+ * 3. 音频只用限制某一种格式
+ * 4. 只支持单个上传,因为多个上传没有办法去处理,即有视频,图片等
+ */
 export default defineComponent({
-  name: 'upload-cover',
-  setup() {
-    return () => <div class={styles.uploadCover}></div>;
+  name: 'upload-file',
+  props: {
+    fileList: {
+      type: String,
+      default: ''
+    },
+    imageList: {
+      type: Array,
+      default: () => []
+    },
+    accept: {
+      // 支持类型
+      type: String,
+      default: '.jpg,.png,.jpeg,.gif'
+    },
+    max: {
+      type: Number as PropType<number>,
+      default: 1
+    },
+    multiple: {
+      type: Boolean as PropType<boolean>,
+      default: false
+    },
+    disabled: {
+      type: Boolean as PropType<boolean>,
+      default: false
+    },
+    bucketName: {
+      type: String,
+      default: 'gyt'
+    },
+    directoryDnd: {
+      type: Boolean as PropType<boolean>,
+      default: false
+    },
+    path: {
+      type: String,
+      default: ''
+    },
+    fileName: {
+      type: String,
+      default: ''
+    },
+    cropper: {
+      // 是否裁切, 只有图片才支持 - 失效(不支持)
+      type: Boolean as PropType<boolean>,
+      default: false
+    },
+    options: {
+      type: Object,
+      default: () => {
+        return {
+          viewMode: 0,
+          autoCrop: true, //是否默认生成截图框
+          enlarge: 1, //  图片放大倍数
+          autoCropWidth: 200, //默认生成截图框宽度
+          autoCropHeight: 200, //默认生成截图框高度
+          fixedBox: false, //是否固定截图框大小 不允许改变
+          previewsCircle: true, //预览图是否是原图形
+          title: '上传图片'
+        };
+      }
+    }
+  },
+  emits: [
+    'update:fileList',
+    'close',
+    'readFileInputEventAsArrayBuffer',
+    'remove',
+    'finished'
+  ],
+  setup(props, { emit, expose, slots }) {
+    const ossUploadUrl = `https://${props.bucketName}.ks3-cn-beijing.ksyuncs.com/`;
+    const message = useMessage();
+    const visiable = ref<boolean>(false);
+    const btnLoading = ref<boolean>(false);
+    const tempFiileBuffer = ref();
+    const uploadRef = ref();
+    const state = reactive([
+      // {
+      //   policy: '',
+      //   signature: '',
+      //   key: '',
+      //   KSSAccessKeyId: '',
+      //   acl: 'public-read',
+      //   name: ''
+      // }
+    ]) as any;
+
+    const fileListRef = ref<UploadFileInfo[]>([]);
+    const initFileList = () => {
+      if (props.fileList) {
+        const splitName = props.fileList.split('/');
+        fileListRef.value = [
+          {
+            id: new Date().getTime().toString(),
+            name: splitName[splitName.length - 1],
+            status: 'finished',
+            url: props.fileList
+          }
+        ];
+      } else {
+        fileListRef.value = [];
+      }
+    };
+    initFileList();
+    watch(
+      () => props.imageList,
+      () => {
+        initFileList();
+      }
+    );
+    watch(
+      () => props.fileList,
+      () => {
+        initFileList();
+      }
+    );
+    const handleClearFile = () => {
+      uploadRef.value?.clear();
+    };
+    expose({
+      handleClearFile
+    });
+
+    const CropperModal = ref();
+    const onBeforeUpload = async (options: any) => {
+      const file = options.file;
+      // 文件大小
+      let isLt2M = true;
+
+      const type = file.type.includes('image')
+        ? NaturalTypeEnum.IMG
+        : file.type.includes('audio')
+        ? NaturalTypeEnum.SONG
+        : NaturalTypeEnum.VIDEO;
+      if (!file.type.includes('image')) {
+        message.error('上传文件格式错误');
+        return false;
+      }
+      const size = type === 'IMG' ? 2 : type === 'SONG' ? 20 : 500;
+      if (size) {
+        isLt2M = file.file.size / 1024 / 1024 < size;
+        if (!isLt2M) {
+          message.error(`文件大小不能超过${size}M`);
+          return false;
+        }
+      }
+
+      if (!isLt2M) {
+        return isLt2M;
+      }
+      // 是否裁切
+      // if (props.cropper && type === 'IMG') {
+      //   getBase64(file.file, (imageUrl: any) => {
+      //     const target = Object.assign({}, props.options, {
+      //       img: imageUrl,
+      //       name: file.file.name // 上传文件名
+      //     });
+      //     visiable.value = true;
+
+      //     setTimeout(() => {
+      //       CropperModal.value?.edit(target);
+      //     }, 100);
+      //   });
+      //   return false;
+      // }
+
+      // try {
+      //   btnLoading.value = true;
+      //   const name = file.file.name;
+      //   const suffix = name.slice(name.lastIndexOf('.'));
+      //   const fileName = `${props.path}${
+      //     props.fileName || Date.now() + suffix
+      //   }`;
+      //   const obj = {
+      //     filename: fileName,
+      //     bucketName: props.bucketName,
+      //     postData: {
+      //       filename: fileName,
+      //       acl: 'public-read',
+      //       key: fileName,
+      //       unknowValueField: []
+      //     }
+      //   };
+      //   const { data } = await policy(obj);
+
+      //   state.push({
+      //     id: file.id,
+      //     tempFiileBuffer: file.file,
+      //     policy: data.policy,
+      //     signature: data.signature,
+      //     acl: 'public-read',
+      //     key: fileName,
+      //     KSSAccessKeyId: data.kssAccessKeyId,
+      //     name: fileName
+      //   });
+
+      //   // tempFiileBuffer.value = file.file;
+      // } catch {
+      //   //
+      //   // message.error('上传失败')
+      //   btnLoading.value = false;
+      //   return false;
+      // }
+      return true;
+    };
+    const getBase64 = async (img: any, callback: any) => {
+      const reader = new FileReader();
+      reader.addEventListener('load', () => callback(reader.result));
+      reader.readAsDataURL(img);
+    };
+
+    const onFinish = (options: any) => {
+      console.log(options, 'onFinish');
+      onFinishAfter(options);
+    };
+    const onFinishAfter = async (options: any) => {
+      const item = state.find((c: any) => c.id == options.file.id);
+      const url = ossUploadUrl + item.key;
+      const type = formatUrlType(url);
+      let coverImg = '';
+      if (type === 'IMG') {
+        coverImg = url;
+      } else if (type === 'SONG') {
+        coverImg = PageEnum.SONG_DEFAULT_COVER;
+      } else if (type === 'VIDEO') {
+        // 获取视频封面图
+        coverImg = await getVideoCoverImg(item.tempFiileBuffer);
+      }
+      emit('update:fileList', url);
+      emit('readFileInputEventAsArrayBuffer', item.tempFiileBuffer);
+      console.log(url, 'url onFinishAfter');
+      emit('finished', {
+        coverImg,
+        content: url
+      });
+
+      options.file.url = url;
+      visiable.value = false;
+      btnLoading.value = false;
+    };
+    const getVideoMsg = (file: any) => {
+      return new Promise(resolve => {
+        // let dataURL = '';
+        const videoElement = document.createElement('video');
+        videoElement.currentTime = 1;
+        videoElement.src = URL.createObjectURL(file);
+        videoElement.addEventListener('loadeddata', function () {
+          const canvas: any = document.createElement('canvas'),
+            width = videoElement.videoWidth, //canvas的尺寸和图片一样
+            height = videoElement.videoHeight;
+          canvas.width = width;
+          canvas.height = height;
+          canvas.getContext('2d').drawImage(videoElement, 0, 0, width, height); //绘制canvas
+          // dataURL = canvas.toDataURL('image/jpeg'); //转换为base64
+          console.log(canvas);
+          canvas.toBlob((blob: any) => {
+            // console.log(blob);
+            resolve(blob);
+          });
+        });
+      });
+    };
+
+    const getVideoCoverImg = async (file: any) => {
+      try {
+        btnLoading.value = true;
+        const imgBlob: any = await getVideoMsg(file || tempFiileBuffer.value);
+        const fileName = `${props.path}${Date.now() + '.png'}`;
+        const obj = {
+          filename: fileName,
+          bucketName: props.bucketName,
+          postData: {
+            filename: fileName,
+            acl: 'public-read',
+            key: fileName,
+            unknowValueField: []
+          }
+        };
+        const { data } = await policy(obj);
+
+        const fileParams = {
+          policy: data.policy,
+          signature: data.signature,
+          key: fileName,
+          acl: 'public-read',
+          KSSAccessKeyId: data.kssAccessKeyId,
+          name: fileName
+        } as any;
+        const formData = new FormData();
+        for (const key in fileParams) {
+          formData.append(key, fileParams[key]);
+        }
+
+        formData.append('file', imgBlob);
+        await axios.post(ossUploadUrl, formData);
+
+        const url = ossUploadUrl + fileName;
+        return url;
+      } finally {
+        btnLoading.value = false;
+      }
+    };
+
+    const onRemove = async () => {
+      emit('update:fileList', '');
+      emit('remove');
+      btnLoading.value = false;
+    };
+
+    // 裁切失败
+    // const cropperNo = () => {}
+    // 裁切成功
+    const cropperOk = async (blob: any) => {
+      try {
+        const fileName = `${props.path}${
+          props.fileName || new Date().getTime() + '.png'
+        }`;
+        const obj = {
+          filename: fileName,
+          bucketName: props.bucketName,
+          postData: {
+            filename: fileName,
+            acl: 'public-read',
+            key: fileName,
+            unknowValueField: []
+          }
+        };
+        const { data } = await policy(obj);
+
+        state.policy = data.policy;
+        state.signature = data.signature;
+        state.key = fileName;
+        state.KSSAccessKeyId = data.kssAccessKeyId;
+        state.name = fileName;
+
+        const formData = new FormData();
+        for (const key in state) {
+          formData.append(key, state[key]);
+        }
+        formData.append('file', blob);
+
+        await axios.post(ossUploadUrl, formData).then(() => {
+          const url = ossUploadUrl + state.key;
+          const splitName = url.split('/');
+          fileListRef.value = [
+            {
+              id: new Date().getTime().toString(),
+              name: splitName[splitName.length - 1],
+              status: 'finished',
+              url: url
+            }
+          ];
+          emit('update:fileList', url);
+          emit('finished', {
+            coverImg: url,
+            content: url
+          });
+          visiable.value = false;
+        });
+      } catch {
+        return false;
+      }
+    };
+
+    const onCustomRequest = ({
+      file,
+      // data,
+      // headers,
+      // withCredentials,
+      action,
+      onFinish,
+      onError,
+      onProgress
+    }: UploadCustomRequestOptions) => {
+      // const item = state.find((c: any) => {
+      //   return c.id == file.id;
+      // });
+
+      // item.file = file;
+      // onFileUpload({ file, action, data: item, onProgress, onFinish, onError });
+      console.log(file);
+    };
+
+    return () => (
+      <div class={styles.uploadFile}>
+        <div class={styles.uploadHeader}>
+          <div class={styles.headerItem}>上传封面</div>
+        </div>
+
+        <div class={styles.uploadContainer}>
+          <NUpload
+            ref={uploadRef}
+            action={ossUploadUrl}
+            customRequest={onCustomRequest}
+            v-model:fileList={fileListRef.value}
+            accept=".jpg,jpeg,.png"
+            multiple={false}
+            max={1}
+            showFileList={false}
+            showPreviewButton
+            onBeforeUpload={(options: any) => onBeforeUpload(options)}
+            onFinish={(options: any) => {
+              onFinish(options);
+            }}
+            onRemove={(options: any) => onRemove()}>
+            <NUploadDragger>
+              <div class={styles.uploadBtn}>
+                <img src={iconUploadAdd} class={styles.iconUploadAdd} />
+                <p>点击或将图片拖至此区域</p>
+                <span>(建议比例16:9)</span>
+              </div>
+            </NUploadDragger>
+          </NUpload>
+        </div>
+
+        <div class={styles.uploadBtnGroup}>
+          <NButton type="default">重新选择</NButton>
+
+          <NSpace>
+            <NButton type="default">取消</NButton>
+            <NButton type="primary">保存封面</NButton>
+          </NSpace>
+        </div>
+        <NModal
+          v-model:show={visiable.value}
+          preset="dialog"
+          showIcon={false}
+          class={['modalTitle background']}
+          title="上传图片"
+          style={{ width: '800px' }}>
+          {/* @cropper-no="error" @cropper-ok="success" */}
+          <Copper
+            ref={CropperModal}
+            onClose={() => (visiable.value = false)}
+            onCropperOk={cropperOk}
+          />
+        </NModal>
+      </div>
+    );
   }
 });