ソースを参照

Merge branch 'iteration-upload-0327' into jenkins

lex 1 年間 前
コミット
a30379c00d

+ 3 - 1
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() {
@@ -422,12 +423,13 @@ export default defineComponent({
                   id: null
                 });
               });
-              state.editList = temp;
+              state.editList.push(...temp);
               state.uploadStatus = true;
               state.editStatus = false;
             }}
           />
         </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');

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

@@ -0,0 +1,115 @@
+.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;
+    }
+
+    .cropper-view-box {
+      outline-color: #198CFE !important;
+    }
+  }
+}
+
+.imgCropperSection {
+  width: 633px;
+  height: 407px;
+  overflow: hidden;
+
+  img {
+    max-width: 100%;
+  }
+}
+
+.uploadBtnGroup {
+  padding: 17px 27px;
+  display: flex;
+  justify-content: space-between;
+
+  :global {
+    .n-button {
+      border-radius: 8px;
+      font-size: max(15px, 13Px);
+      min-width: 97px;
+    }
+  }
+}

+ 308 - 0
src/views/natural-resources/components/my-resources/upload-cover/index.tsx

@@ -0,0 +1,308 @@
+import {
+  NButton,
+  NModal,
+  NSpace,
+  NSpin,
+  NUpload,
+  NUploadDragger,
+  useMessage
+} from 'naive-ui';
+import {
+  defineComponent,
+  reactive,
+  ref,
+  nextTick,
+  onMounted,
+  watch,
+  computed
+} from 'vue';
+import Cropper from 'cropperjs';
+import 'cropperjs/dist/cropper.css';
+import styles from './index.module.less';
+import iconUploadAdd from '../../../images/icon-upload2.png';
+import { NaturalTypeEnum } from '@/enums/pageEnum';
+import { getUploadSign, onOnlyFileUpload } from '/src/helpers/oss-file-upload';
+import { formatUrlType } from '../upload-modal';
+
+/**
+ * 1. 图片上传可以进行裁剪
+ */
+export default defineComponent({
+  name: 'upload-file',
+  props: {
+    img: {
+      type: String,
+      default: ''
+    },
+    accept: {
+      // 支持类型
+      type: String,
+      default: '.jpg,.png,.jpeg,.gif'
+    },
+    bucketName: {
+      type: String,
+      default: 'gyt'
+    },
+    path: {
+      type: String,
+      default: ''
+    },
+    fileName: {
+      type: String,
+      default: ''
+    },
+    options: {
+      type: Object,
+      default: () => {
+        return {
+          viewMode: 0,
+          autoCrop: true, //是否默认生成截图框
+          enlarge: 1, //  图片放大倍数
+          autoCropWidth: 640, //默认生成截图框宽度
+          autoCropHeight: 360, //默认生成截图框高度
+          fixedBox: false, //是否固定截图框大小 不允许改变
+          previewsCircle: true, //预览图是否是原图形
+          title: '上传图片'
+        };
+      }
+    }
+  },
+  emits: ['close', 'confirm'],
+  setup(props, { emit }) {
+    const imgCropper = ref();
+    const ossUploadUrl = `https://${props.bucketName}.ks3-cn-beijing.ksyuncs.com/`;
+    const message = useMessage();
+    const visiable = ref<boolean>(false);
+    const btnLoading = ref<boolean>(false);
+    const uploadRef = ref();
+    const copperOptions = ref({
+      img: '', //裁剪图片的地址
+      autoCrop: true, //是否默认生成截图框
+      autoCropWidth: 180, //默认生成截图框宽度
+      autoCropHeight: 180, //默认生成截图框高度
+      fixedBox: true, //是否固定截图框大小 不允许改变
+      full: false,
+      enlarge: 1, // 是否按照截图框比例输出 默认为1
+      previewsCircle: true, //预览图是否是原圆形
+      centerBox: true,
+      outputType: 'png',
+      title: '修改头像',
+      name: null // 文件名称
+    } as any);
+    const state = reactive({
+      myCropper: null as any,
+      cropperReady: false
+    }) as any;
+
+    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 (type !== NaturalTypeEnum.IMG) {
+        message.error('上传文件格式错误');
+        return false;
+      }
+      const size = 10; //type === 'IMG' ? 10 : type === 'SONG' ? 20 : 500;
+      if (size) {
+        isLt2M = file.file.size / 1024 / 1024 < size;
+        if (!isLt2M) {
+          message.error(`图片大小不能超过${size}M`);
+          return false;
+        }
+      }
+      getBase64(file.file, (imageUrl: any) => {
+        cropperBefore(imageUrl, file.file.name);
+      });
+      return false;
+    };
+    const getBase64 = async (img: any, callback: any) => {
+      const reader = new FileReader();
+      reader.addEventListener('load', () => callback(reader.result));
+      reader.readAsDataURL(img);
+    };
+
+    const cropperBefore = (imageUrl: string, name?: string) => {
+      const target = Object.assign({}, props.options, {
+        img: imageUrl,
+        name // 上传文件名
+      });
+
+      visiable.value = true;
+      copperOptions.value = Object.assign({}, copperOptions.value, target);
+      nextTick(() => {
+        initImgCropper();
+      });
+    };
+
+    const initImgCropper = () => {
+      try {
+        state.cropperReady = true;
+        state.myCropper = new Cropper(imgCropper.value, {
+          viewMode: 1, //定义裁剪器的视图模式。如果将viewMode设置为0,则裁剪框可以延伸到画布外部,而值为1、2或3将限制裁剪框的大小为画布的大小。viewMode为2或3会将画布限制为容器。请注意,如果画布和容器的比例相同,则2和3之间没有差别。
+          dragMode: 'move', //定义的拖动模式裁剪器.canvas和容器一样,2和3没有区别。move:移动画布 crop:创建新的裁剪框(默认) none:什么也不做
+          //定义裁剪框的固定纵横比。默认情况下,裁剪框为自由比率。
+          aspectRatio:
+            copperOptions.value.autoCropWidth /
+            copperOptions.value.autoCropHeight,
+          initialAspectRatio: 1,
+          autoCropArea: 1, //定义0到1之间的fA编号。定义自动裁剪区域大小(百分比)。默认0.8
+          cropBoxMovable: false, //允许通过拖动移动裁剪框。默认true
+          cropBoxResizable: false, //以通过拖动来调整裁剪框的大小 默认true
+          background: true, //显示容器的网格背景
+          movable: true, //移动图像
+          modal: true,
+          preview: '.before',
+          ready: () => {
+            state.cropperReady = false;
+          }
+        });
+      } catch {
+        //
+      }
+    };
+
+    // 裁切成功
+    const onSubmit = () => {
+      state.myCropper
+        .getCroppedCanvas({
+          imageSmoothingQuality: 'high'
+        })
+        .toBlob((blob: any) => {
+          btnLoading.value = true;
+          cropperOk(blob);
+        });
+    };
+    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
+          }
+        };
+
+        const { data } = await getUploadSign(obj);
+        const formData = {
+          policy: data.policy,
+          signature: data.signature,
+          acl: 'public-read',
+          key: fileName,
+          KSSAccessKeyId: data.kssAccessKeyId,
+          name: fileName,
+          file: blob
+        };
+        const res = await onOnlyFileUpload(ossUploadUrl, formData);
+        emit('confirm', res);
+        emit('close');
+      } catch {
+        //
+      }
+      btnLoading.value = false;
+    };
+
+    watch(
+      () => props.img,
+      () => {
+        if (props.img && formatUrlType(props.img) === NaturalTypeEnum.IMG) {
+          cropperBefore(props.img);
+        }
+      }
+    );
+
+    onMounted(() => {
+      if (props.img && formatUrlType(props.img) === NaturalTypeEnum.IMG) {
+        cropperBefore(props.img);
+      }
+    });
+    return () => (
+      <div class={styles.uploadFile}>
+        <div class={styles.uploadHeader}>
+          <div class={styles.headerItem}>上传封面</div>
+        </div>
+
+        {!visiable.value ? (
+          <div class={styles.uploadContainer}>
+            <NUpload
+              ref={uploadRef}
+              accept={props.accept}
+              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.uploadContainer}>
+            <NSpin show={state.cropperReady}>
+              <div class={styles.imgCropperSection}>
+                {copperOptions.value?.img && (
+                  <img
+                    ref={imgCropper}
+                    src={copperOptions.value?.img}
+                    style={{
+                      opacity: state.myCropper?.ready ? '1' : '0'
+                    }}
+                    alt=""
+                  />
+                )}
+              </div>
+            </NSpin>
+          </div>
+        )}
+
+        <div class={styles.uploadBtnGroup}>
+          {visiable.value ? (
+            <NButton
+              type="default"
+              onClick={() => {
+                copperOptions.value.img = '';
+                visiable.value = false;
+              }}>
+              重新选择
+            </NButton>
+          ) : (
+            <span></span>
+          )}
+
+          <NSpace>
+            <NButton type="default" onClick={() => emit('close')}>
+              取消
+            </NButton>
+            <NButton
+              type="primary"
+              onClick={() => onSubmit()}
+              disabled={!visiable.value}
+              loading={btnLoading.value}>
+              保存封面
+            </NButton>
+          </NSpace>
+        </div>
+      </div>
+    );
+  }
+});

+ 28 - 1
src/views/natural-resources/components/my-resources/upload-modal/index.module.less

@@ -66,12 +66,22 @@
       // background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.7) 100%);
       width: 100%;
       border-radius: 0 0 10px 10px;
-      justify-content: flex-end;
+      justify-content: space-between;
       padding-right: 12px;
+      padding-left: 12px;
       z-index: 99;
       background: rgba(0, 0, 0, 0.3);
       backdrop-filter: blur(1px);
 
+      .changeCover {
+        background: #FFFFFF;
+        border-radius: 4px;
+        color: #131415 !important;
+        --n-height: 24px !important;
+        --n-padding: 0 8px !important;
+        font-size: max(14px, 12Px);
+      }
+
       :global {
         .n-switch {
           margin-left: 8px;
@@ -167,4 +177,21 @@
       margin-bottom: 20px;
     }
   }
+}
+
+
+.uploadCover {
+  width: 920px;
+
+  :global {
+
+    .n-card-header {
+      padding-top: 0;
+      padding-bottom: 0;
+
+      .n-card-header__close {
+        margin-top: 65px;
+      }
+    }
+  }
 }

+ 51 - 10
src/views/natural-resources/components/my-resources/upload-modal/index.tsx

@@ -13,6 +13,7 @@ import {
   NFormItem,
   NImage,
   NInput,
+  NModal,
   NScrollbar,
   NSelect,
   NSpace,
@@ -30,6 +31,7 @@ import iconUploadDelete from '../../../images/btn-remove.png';
 import { materialSave, materialUpdateAll } from '../../../api';
 import { NaturalTypeEnum } from '@/enums/pageEnum';
 import { scrollToErrorForm } from '/src/utils';
+import UploadCover from '../upload-cover';
 
 // 判断链接后辍
 export const formatUrlType = (url: string) => {
@@ -63,8 +65,8 @@ export default defineComponent({
     const formRef = ref();
     const message = useMessage();
 
-    const uploadRef = ref();
-    const uploadList = ref([] as any);
+    // const uploadRef = ref();
+    // const uploadList = ref([] as any);
     const uploadForms = reactive({
       list: [] as any[],
       uploading: false,
@@ -72,6 +74,12 @@ export default defineComponent({
       name: '',
       subjectIds: [] as any
     });
+    const changeCover = reactive({
+      uploadCoverStatus: false,
+      uploadType: '',
+      uploadImg: null as any,
+      uploadIndex: 0
+    });
 
     // 判断类型
     const formatType = (type: string) => {
@@ -220,14 +228,29 @@ export default defineComponent({
                     />
 
                     <div class={styles.commonType}>
-                      是否公开
-                      <NSwitch
-                        size="small"
-                        v-model:value={item.openFlag}
-                        disabled={
-                          item.sourceFrom === 'TEACHER' && item.type === 'MUSIC'
-                        }
-                      />
+                      <NButton
+                        class={styles.changeCover}
+                        type="default"
+                        bordered={false}
+                        onClick={() => {
+                          changeCover.uploadIndex = index;
+                          changeCover.uploadImg = item.coverImg;
+                          changeCover.uploadType = item.type;
+                          changeCover.uploadCoverStatus = true;
+                        }}>
+                        更换封面
+                      </NButton>
+                      <div>
+                        是否公开
+                        <NSwitch
+                          size="small"
+                          v-model:value={item.openFlag}
+                          disabled={
+                            item.sourceFrom === 'TEACHER' &&
+                            item.type === 'MUSIC'
+                          }
+                        />
+                      </div>
                     </div>
                   </div>
                   <NFormItem
@@ -387,6 +410,24 @@ export default defineComponent({
             确定
           </NButton>
         </NSpace>
+
+        <NModal
+          v-model:show={changeCover.uploadCoverStatus}
+          preset="card"
+          showIcon={false}
+          class={['modalTitle ', styles.uploadCover]}
+          blockScroll={false}>
+          <UploadCover
+            img={changeCover.uploadImg}
+            onClose={() => (changeCover.uploadCoverStatus = false)}
+            onConfirm={(val: any) => {
+              if (changeCover.uploadType === 'IMG') {
+                uploadForms.list[changeCover.uploadIndex].content = val;
+              }
+              uploadForms.list[changeCover.uploadIndex].coverImg = val;
+            }}
+          />
+        </NModal>
       </div>
     );
   }