|
|
@@ -15,7 +15,12 @@
|
|
|
<sc-video v-if="isVideo(file.mineType)" :src="'/api/folder/' + file.path" showMask @play="videoPlay"></sc-video>
|
|
|
|
|
|
<div class="sc-upload__img-actions" v-if="!disabled">
|
|
|
- <span class="del" @click="handleRemove()"><el-icon><el-icon-delete /></el-icon></span>
|
|
|
+ <el-button class="download" :loading="loading" type="primary" @click="handleDownload">
|
|
|
+ <sc-iconify icon="ant-design:download-outlined"></sc-iconify>
|
|
|
+ </el-button>
|
|
|
+ <el-button class="del" :loading="loading" type="danger" @click="handleRemove">
|
|
|
+ <sc-iconify icon="ant-design:delete-outlined"></sc-iconify>
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<el-upload v-if="!file" class="uploader" ref="uploader"
|
|
|
@@ -50,195 +55,207 @@
|
|
|
</el-dialog>
|
|
|
</div>
|
|
|
|
|
|
- <sc-video-viewer v-if="showVideoViewer" :videoUrl="previewVideoUrl" hideOnModal @close="showVideoViewer = false"></sc-video-viewer>
|
|
|
+ <file-viewer v-if="showViewer" ref="fileViewer" @closed="showViewer = false"></file-viewer>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
- import config from "@/config/upload";
|
|
|
-
|
|
|
- export default {
|
|
|
- props: {
|
|
|
- modelValue: { type: Object, default: () => {} },
|
|
|
- width: { type: Number, default: 148 },
|
|
|
- height: { type: Number, default: 148 },
|
|
|
- title: { type: String, default: "" },
|
|
|
- accept: { type: String, default: "image/gif, image/jpeg, image/png, video/mp4 , video/avi" },
|
|
|
- icon: { type: String, default: "el-icon-plus" },
|
|
|
- maxSize: { type: Number, default: 50 },
|
|
|
- disabled: { type: Boolean, default: false },
|
|
|
- round: { type: Boolean, default: false },
|
|
|
- onSuccess: { type: Function, default: () => { return true } },
|
|
|
- cropper: { type: Boolean, default: false },
|
|
|
- compress: { type: Number, default: 1 },
|
|
|
- aspectRatio: {type: Number, default: NaN }
|
|
|
- },
|
|
|
-
|
|
|
- data() {
|
|
|
- return {
|
|
|
- value: "{}",
|
|
|
- file: null,
|
|
|
- style: {
|
|
|
- width: this.width + "px",
|
|
|
- height: this.height + "px"
|
|
|
- },
|
|
|
- cropperDialogVisible: false,
|
|
|
- cropperFile: null,
|
|
|
-
|
|
|
- showVideoViewer: false,
|
|
|
- previewVideoUrl: ""
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- watch: {
|
|
|
- modelValue(val) {
|
|
|
- this.value = JSON.stringify(val);
|
|
|
- this.newFile(val);
|
|
|
- },
|
|
|
-
|
|
|
- value(val) {
|
|
|
- this.$emit("update:modelValue", JSON.parse(val));
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- mounted() {
|
|
|
- if (this.modelValue) {
|
|
|
- this.value = JSON.stringify(this.modelValue);
|
|
|
- this.newFile(this.modelValue);
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- methods: {
|
|
|
- isImage(type) {
|
|
|
- return config.imageIncludes(type);
|
|
|
- },
|
|
|
-
|
|
|
- isVideo(type) {
|
|
|
- return config.videoIncludes(type);
|
|
|
- },
|
|
|
-
|
|
|
- newFile(data) {
|
|
|
- this.file = Object.keys(data).length ? { status: "success", ...data } : null;
|
|
|
- },
|
|
|
-
|
|
|
- cropperSave() {
|
|
|
- this.$refs.cropper.getCropFile(file => {
|
|
|
-
|
|
|
- file.uid = this.cropperFile.uid;
|
|
|
- this.cropperFile.raw = file;
|
|
|
-
|
|
|
- this.file = this.cropperFile;
|
|
|
- this.file.tempFile = URL.createObjectURL(this.file.raw);
|
|
|
- this.$refs.uploader.submit();
|
|
|
-
|
|
|
- }, this.cropperFile.name, this.cropperFile.type);
|
|
|
- this.cropperDialogVisible = false;
|
|
|
- },
|
|
|
-
|
|
|
- cropperClosed() {
|
|
|
- URL.revokeObjectURL(this.cropperFile.tempCropperFile);
|
|
|
- delete this.cropperFile.tempCropperFile;
|
|
|
- },
|
|
|
-
|
|
|
- handleRemove() {
|
|
|
- const file = JSON.parse(this.value)
|
|
|
- this.$confirm(`是否移除 ${file.name}? 此操作不可逆!`, "提示", {
|
|
|
- type: "warning",
|
|
|
- confirmButtonText: "移除"
|
|
|
- }).then(() => {
|
|
|
- if (file.id) {
|
|
|
- this.$API.common.folder.rm(file.id).then(res => {
|
|
|
- if (res.code == 200) this.clearFiles();
|
|
|
- }).catch(() => {});
|
|
|
- } else this.clearFiles();
|
|
|
- }).catch(() => {});
|
|
|
- },
|
|
|
-
|
|
|
- clearFiles() {
|
|
|
- URL.revokeObjectURL(this.file.tempFile);
|
|
|
- this.value = "{}";
|
|
|
- this.file = null;
|
|
|
- this.$nextTick(() => this.$refs.uploader.clearFiles());
|
|
|
- },
|
|
|
-
|
|
|
- change(file, files) {
|
|
|
- if (files.length > 1) files.splice(0, 1);
|
|
|
-
|
|
|
- if (this.cropper && file.status == "ready") {
|
|
|
- if (!this.isImage(file.raw.type)) return false;
|
|
|
- this.cropperFile = file;
|
|
|
- this.cropperFile.tempCropperFile = URL.createObjectURL(file.raw);
|
|
|
- this.cropperDialogVisible = true;
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- this.file = file;
|
|
|
- if (file.status == "ready") file.tempFile = URL.createObjectURL(file.raw);
|
|
|
- },
|
|
|
-
|
|
|
- before(file) {
|
|
|
- if (!this.isImage(file.type) && !this.isVideo(file.type)) {
|
|
|
- this.$message.warning({ title: "上传文件警告", message: "选择的文件非图像类/视频类文件" });
|
|
|
- this.clearFiles();
|
|
|
- return false;
|
|
|
- }
|
|
|
- const maxSize = file.size / 1024 / 1024 < this.maxSize;
|
|
|
- if (!maxSize) {
|
|
|
- this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
|
|
|
- this.clearFiles();
|
|
|
- return false;
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- handleExceed(files) {
|
|
|
- const file = files[0];
|
|
|
- file.uid = genFileId();
|
|
|
- this.$refs.uploader.handleStart(file);
|
|
|
- },
|
|
|
-
|
|
|
- success(res, file) {
|
|
|
- // 释放内存删除blob
|
|
|
- URL.revokeObjectURL(file.tempFile);
|
|
|
- delete file.tempFile;
|
|
|
- let os = this.onSuccess(res, file);
|
|
|
- if (os != undefined && os == false) {
|
|
|
- this.$nextTick(() => {
|
|
|
- this.file = null;
|
|
|
- this.value = "{}";
|
|
|
- });
|
|
|
- return false;
|
|
|
- }
|
|
|
- file.name = res.fileName;
|
|
|
- file.path = res.path;
|
|
|
- file.mineType = res.mineType;
|
|
|
- this.value = JSON.stringify({ path: res.path, name: res.fileName, mineType: res.mineType });
|
|
|
- },
|
|
|
-
|
|
|
- error(message) {
|
|
|
- this.$nextTick(() => this.clearFiles());
|
|
|
- this.$notify.error({ title: "上传文件未成功", message });
|
|
|
- },
|
|
|
-
|
|
|
- request(param) {
|
|
|
- const data = new FormData();
|
|
|
- data.append(param.filename, param.file);
|
|
|
-
|
|
|
- this.$API.common.folder.up(data, {
|
|
|
- onUploadProgress: e => {
|
|
|
- const percent = parseInt(((e.loaded / e.total) * 100) | 0, 10);
|
|
|
- param.onProgress({ percent });
|
|
|
- }
|
|
|
- }).then(res => {
|
|
|
- if (res.code == 200) param.onSuccess({ path: res.expands.file, fileName: param.file.name, mineType: param.file.type });
|
|
|
- else param.onError(res.message || "未知错误");
|
|
|
- }).catch(err => param.onError(err));
|
|
|
- },
|
|
|
-
|
|
|
- videoPlay() {
|
|
|
- this.showVideoViewer = true;
|
|
|
- this.previewVideoUrl = "/api/folder/" + this.file.path;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+import { fileTypes } from "./main";
|
|
|
+
|
|
|
+export default {
|
|
|
+ props: {
|
|
|
+ modelValue: { type: Object, default: () => {} },
|
|
|
+ width: { type: Number, default: 148 },
|
|
|
+ height: { type: Number, default: 148 },
|
|
|
+ title: { type: String, default: "" },
|
|
|
+ accept: { type: String, default: "image/gif, image/jpeg, image/png, video/mp4 , video/avi" },
|
|
|
+ icon: { type: String, default: "el-icon-plus" },
|
|
|
+ maxSize: { type: Number, default: 50 },
|
|
|
+ disabled: { type: Boolean, default: false },
|
|
|
+ round: { type: Boolean, default: false },
|
|
|
+ onSuccess: { type: Function, default: () => { return true } },
|
|
|
+ cropper: { type: Boolean, default: false },
|
|
|
+ compress: { type: Number, default: 1 },
|
|
|
+ aspectRatio: {type: Number, default: NaN }
|
|
|
+ },
|
|
|
+
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ value: "{}",
|
|
|
+ file: null,
|
|
|
+ style: {
|
|
|
+ width: this.width + "px",
|
|
|
+ height: this.height + "px"
|
|
|
+ },
|
|
|
+ cropperDialogVisible: false,
|
|
|
+ cropperFile: null,
|
|
|
+
|
|
|
+ loading: false,
|
|
|
+ showViewer: false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ watch: {
|
|
|
+ modelValue(val) {
|
|
|
+ this.value = JSON.stringify(val);
|
|
|
+ this.newFile(val);
|
|
|
+ },
|
|
|
+
|
|
|
+ value(val) {
|
|
|
+ this.$emit("update:modelValue", JSON.parse(val));
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ mounted() {
|
|
|
+ if (this.modelValue) {
|
|
|
+ this.value = JSON.stringify(this.modelValue);
|
|
|
+ this.newFile(this.modelValue);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ methods: {
|
|
|
+ isImage(type) {
|
|
|
+ return fileTypes[type] == "image"
|
|
|
+ },
|
|
|
+
|
|
|
+ isVideo(type) {
|
|
|
+ return fileTypes[type] == "video"
|
|
|
+ },
|
|
|
+
|
|
|
+ newFile(data) {
|
|
|
+ this.file = Object.keys(data).length ? { status: "success", ...data } : null;
|
|
|
+ },
|
|
|
+
|
|
|
+ cropperSave() {
|
|
|
+ this.$refs.cropper.getCropFile(file => {
|
|
|
+
|
|
|
+ file.uid = this.cropperFile.uid;
|
|
|
+ this.cropperFile.raw = file;
|
|
|
+
|
|
|
+ this.file = this.cropperFile;
|
|
|
+ this.file.tempFile = URL.createObjectURL(this.file.raw);
|
|
|
+ this.$refs.uploader.submit();
|
|
|
+
|
|
|
+ }, this.cropperFile.name, this.cropperFile.type);
|
|
|
+ this.cropperDialogVisible = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ cropperClosed() {
|
|
|
+ URL.revokeObjectURL(this.cropperFile.tempCropperFile);
|
|
|
+ delete this.cropperFile.tempCropperFile;
|
|
|
+ },
|
|
|
+
|
|
|
+ handleRemove() {
|
|
|
+ const file = JSON.parse(this.value)
|
|
|
+ this.$confirm(`是否移除 ${file.name}? 此操作不可逆!`, "提示", {
|
|
|
+ type: "warning",
|
|
|
+ confirmButtonText: "移除"
|
|
|
+ }).then(() => {
|
|
|
+ if (file.id) {
|
|
|
+ this.$API.common.folder.rm(file.id).then(res => {
|
|
|
+ if (res.code == 200) this.clearFiles();
|
|
|
+ }).catch(() => {});
|
|
|
+ } else this.clearFiles();
|
|
|
+ }).catch(() => {});
|
|
|
+ },
|
|
|
+
|
|
|
+ clearFiles() {
|
|
|
+ URL.revokeObjectURL(this.file.tempFile);
|
|
|
+ this.value = "{}";
|
|
|
+ this.file = null;
|
|
|
+ this.$nextTick(() => this.$refs.uploader.clearFiles());
|
|
|
+ },
|
|
|
+
|
|
|
+ change(file, files) {
|
|
|
+ if (files.length > 1) files.splice(0, 1);
|
|
|
+
|
|
|
+ if (this.cropper && file.status == "ready") {
|
|
|
+ if (!this.isImage(file.raw.type)) return false;
|
|
|
+ this.cropperFile = file;
|
|
|
+ this.cropperFile.tempCropperFile = URL.createObjectURL(file.raw);
|
|
|
+ this.cropperDialogVisible = true;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.file = file;
|
|
|
+ if (file.status == "ready") file.tempFile = URL.createObjectURL(file.raw);
|
|
|
+ },
|
|
|
+
|
|
|
+ before(file) {
|
|
|
+ if (!this.isImage(file.type) && !this.isVideo(file.type)) {
|
|
|
+ this.$message.warning({ title: "上传文件警告", message: "选择的文件非图像类/视频类文件" });
|
|
|
+ this.clearFiles();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ const maxSize = file.size / 1024 / 1024 < this.maxSize;
|
|
|
+ if (!maxSize) {
|
|
|
+ this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
|
|
|
+ this.clearFiles();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ handleExceed(files) {
|
|
|
+ const file = files[0];
|
|
|
+ file.uid = genFileId();
|
|
|
+ this.$refs.uploader.handleStart(file);
|
|
|
+ },
|
|
|
+
|
|
|
+ success(res, file) {
|
|
|
+ // 释放内存删除blob
|
|
|
+ URL.revokeObjectURL(file.tempFile);
|
|
|
+ delete file.tempFile;
|
|
|
+ let os = this.onSuccess(res, file);
|
|
|
+ if (os != undefined && os == false) {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.file = null;
|
|
|
+ this.value = "{}";
|
|
|
+ });
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ file.name = res.fileName;
|
|
|
+ file.path = res.path;
|
|
|
+ file.mineType = res.mineType;
|
|
|
+ this.value = JSON.stringify({ path: res.path, name: res.fileName, mineType: res.mineType });
|
|
|
+ },
|
|
|
+
|
|
|
+ error(message) {
|
|
|
+ this.$nextTick(() => this.clearFiles());
|
|
|
+ this.$notify.error({ title: "上传文件未成功", message });
|
|
|
+ },
|
|
|
+
|
|
|
+ request(param) {
|
|
|
+ const data = new FormData();
|
|
|
+ data.append(param.filename, param.file);
|
|
|
+
|
|
|
+ this.$API.common.folder.up(data, {
|
|
|
+ onUploadProgress: e => {
|
|
|
+ const percent = parseInt(((e.loaded / e.total) * 100) | 0, 10);
|
|
|
+ param.onProgress({ percent });
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+ if (res.code == 200) param.onSuccess({ path: res.expands.file, fileName: param.file.name, mineType: param.file.type });
|
|
|
+ else param.onError(res.message || "未知错误");
|
|
|
+ }).catch(err => param.onError(err));
|
|
|
+ },
|
|
|
+
|
|
|
+ videoPlay() {
|
|
|
+ this.showViewer = true;
|
|
|
+ nextTick(() => this.$refs.fileViewer.init(this.file));
|
|
|
+ },
|
|
|
+
|
|
|
+ handleDownload() {
|
|
|
+ this.loading = true;
|
|
|
+ this.$API.common.folder.download(this.file.path).then(res => {
|
|
|
+ this.loading = false;
|
|
|
+ const a = document.createElement("a");
|
|
|
+ const blob = new Blob([res.data], { type: this.file.mineType });
|
|
|
+ a.download = this.file.name;
|
|
|
+ a.href = URL.createObjectURL(blob);
|
|
|
+ a.click();
|
|
|
+ }).catch(() => this.loading = false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
@@ -274,28 +291,23 @@
|
|
|
border: 1px solid var(--el-border-color);
|
|
|
}
|
|
|
.sc-upload__img-actions {
|
|
|
+ z-index: 120;
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
right: 0;
|
|
|
display: none;
|
|
|
}
|
|
|
-.sc-upload__img-actions span {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
+.sc-upload__img-actions .el-button {
|
|
|
width: 25px;
|
|
|
height: 25px;
|
|
|
- cursor: pointer;
|
|
|
- color: #fff;
|
|
|
-}
|
|
|
-.sc-upload__img-actions span i {
|
|
|
- font-size: 12px;
|
|
|
+ padding: 0;
|
|
|
+ border-radius: 0;
|
|
|
}
|
|
|
-.sc-upload__img-actions .del {
|
|
|
- background: #f56c6c;
|
|
|
+.sc-upload__img-actions .el-button + .el-button {
|
|
|
+ margin-left: 0;
|
|
|
}
|
|
|
.sc-upload__img:hover .sc-upload__img-actions {
|
|
|
- display: block;
|
|
|
+ display: flex;
|
|
|
}
|
|
|
.sc-upload__img-slot {
|
|
|
display: flex;
|