| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- <template>
- <div class="sc-upload" :class="{ 'sc-upload-round': round }" :style="style">
- <div v-if="file && file.status != 'success'" class="sc-upload__uploading">
- <div class="sc-upload__progress">
- <el-progress :percentage="file.percentage" text-inside :stroke-width="16" />
- </div>
- <el-image class="image" :src="file.tempFile" fit="cover"></el-image>
- </div>
- <div v-if="file && file.status=='success'" class="sc-upload__img">
- <el-image v-if="isImage(file.mineType)" class="image" :src="'/zcxt/folder/' + file.path" :preview-src-list="['/zcxt/folder/' + file.path]" fit="cover" preview-teleported :z-index="9999">
- <template #placeholder>
- <div class="sc-upload__img-slot">Loading...</div>
- </template>
- </el-image>
- <sc-video v-if="isVideo(file.mineType)" :src="'/zcxt/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>
- </div>
- </div>
- <el-upload v-if="!file" class="uploader" ref="uploader"
- action=""
- :auto-upload="cropper ? false : true"
- :disabled="disabled"
- :show-file-list="false"
- :accept="accept"
- :limit="1"
- :http-request="request"
- :on-change="change"
- :before-upload="before"
- :on-success="success"
- :on-error="error"
- :on-exceed="handleExceed">
- <slot>
- <div class="el-upload--picture-card" :class="disabled && 'is-disabled'">
- <div class="file-empty">
- <el-icon><component :is="icon" /></el-icon>
- <h4 v-if="title">{{ title }}</h4>
- </div>
- </div>
- </slot>
- </el-upload>
- <span style="display:none!important"><el-input v-model="value"></el-input></span>
- <el-dialog title="剪裁" draggable v-model="cropperDialogVisible" :width="580" @closed="cropperClosed" destroy-on-close>
- <sc-cropper :src="cropperFile.tempCropperFile" :compress="compress" :aspectRatio="aspectRatio" ref="cropper"></sc-cropper>
- <template #footer>
- <el-button @click="cropperDialogVisible=false" >取 消</el-button>
- <el-button type="primary" @click="cropperSave">确 定</el-button>
- </template>
- </el-dialog>
- </div>
- <sc-video-viewer v-if="showVideoViewer" :videoUrl="previewVideoUrl" hideOnModal @close="showVideoViewer = false"></sc-video-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 = "/zcxt/folder/" + this.file.path;
- }
- }
- }
- </script>
- <style scoped>
- .el-form-item.is-error .sc-upload .el-upload--picture-card {
- border-color: var(--el-color-danger);
- }
- .sc-upload .el-upload--picture-card {
- width: 100%;
- height: 100%;
- border-radius: 0;
- }
- .sc-upload .el-upload--picture-card.is-disabled {
- border-color: var(--el-border-color-darker);
- cursor: not-allowed;
- }
- .sc-upload .uploader,
- .sc-upload:deep(.el-upload) {
- width: 100%;
- height: 100%;
- justify-content: unset;
- }
- .sc-upload__img {
- width: 100%;
- height: 100%;
- position: relative;
- }
- .sc-upload__img .image {
- width: 100%;
- height: 100%;
- border: 1px solid var(--el-border-color);
- }
- .sc-upload__img-actions {
- position: absolute;
- top: 0;
- right: 0;
- display: none;
- }
- .sc-upload__img-actions span {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 25px;
- height: 25px;
- cursor: pointer;
- color: #fff;
- }
- .sc-upload__img-actions span i {
- font-size: 12px;
- }
- .sc-upload__img-actions .del {
- background: #f56c6c;
- }
- .sc-upload__img:hover .sc-upload__img-actions {
- display: block;
- }
- .sc-upload__img-slot {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 100%;
- height: 100%;
- font-size: 12px;
- background-color: var(--el-fill-color-lighter);
- }
- .sc-upload__uploading {
- width: 100%;
- height: 100%;
- position: relative;
- }
- .sc-upload__progress {
- position: absolute;
- width: 100%;
- height: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- background-color: var(--el-overlay-color-lighter);
- z-index: 1;
- padding: 10px;
- }
- .sc-upload__progress .el-progress {
- width: 100%;
- }
- .sc-upload__uploading .image {
- width: 100%;
- height: 100%;
- }
- .sc-upload .file-empty {
- width: 100%;
- height: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- flex-direction: column;
- }
- .sc-upload .file-empty i {
- font-size: 28px;
- }
- .sc-upload .file-empty h4 {
- font-size: 12px;
- font-weight: normal;
- color: #8c939d;
- margin-top: 8px;
- }
- .sc-upload.sc-upload-round {
- border-radius: 50%;
- overflow: hidden;
- }
- .sc-upload.sc-upload-round .el-upload--picture-card {
- border-radius: 50%;
- }
- .sc-upload.sc-upload-round .sc-upload__img-actions {
- top: auto;
- left: 0;
- right: 0;
- bottom: 0;
- }
- .sc-upload.sc-upload-round .sc-upload__img-actions span {
- width: 100%;
- }
- </style>
|