|
@@ -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>
|
|
|
+ );
|
|
|
+ }
|
|
|
+});
|