效果图
添加按钮是一个opacity为0的input框儿。。。。好像没别的特别的地方了。。嗷。。图片
嗷对!那个裁剪的组件是从vue-element-admin里面择出来的,做了一些样式上小小的改动。
组件代码
<template>
<div>
<div :ref="refName" class="">
<transition-group name="list" tag="div" class="file-list">
<div
v-for="(file, index) in fileList"
:key="file.url || file"
:id="file.url"
class="file-item"
:style="{
width: width + 'px',
height: height + 'px',
margin: margin(index),
}"
@mouseenter="$set(fileList[index], 'wrap', true)"
@mouseleave="$set(fileList[index], 'wrap', false)"
@click="(e) => onClick({ e, index })"
>
<img
:src="file.url || file"
alt=""
class="image"
:id="file.url"
v-if="type === 'image'"
/>
<video
v-if="type === 'video'"
:src="file.url"
class="file-item"
:style="{
width: width + 'px',
height: height + 'px',
margin: margin(index),
}"
></video>
<transition name="fade">
<div class="file-wrap" v-show="file.wrap">
<div class="icon-view">
<i
class="fa fa-trash"
aria-hidden
v-if="!disabled"
></i>
<i class="fa fa-search-plus" aria-hidden></i>
</div>
</div>
</transition>
</div>
</transition-group>
<!-- 图标 + -->
<transition name="fade">
<div
v-if="fileList.length < limit || !limit"
:class="['plus-view file-item', disabled ? 'disabled' : '']"
:style="{
width: width + 'px',
height: height + 'px',
}"
@click="crop ? uploadCropImage() : ''"
>
<i class="fa fa-plus icon-plus" aria-hidden="true" />
<input
class="input"
type="file"
:multiple="multiple"
@change="onChange"
v-if="!disabled && !crop"
/>
</div>
</transition>
</div>
<!-- preview image -->
<njx-dialog :show.sync="previewImageShow" class="dialog">
<img :src="currentUrl" alt="" class="preview-image" />
<video :src="currentUrl" class="preview-image" autoplay controls></video>
</njx-dialog>
<!-- 裁剪区域 -->
<njx-upload-crop-image
:value="cropShow"
:width="cropImageWidth"
:height="cropImageHeight"
@crop-success="onCrop"
@close="cropShow = false"
/>
</div>
</template>
<script>
export default {
name: "njx-upload",
props: {
refName: {
type: String,
default: "njxUpload",
},
// 上传文件类型
type: {
type: String,
default: "image",
},
// 初始文件列表
list: {
type: Array,
default: () => [],
},
disabled: {
type: Boolean,
default: false,
},
// 文件数量限制,默认为0,即不限制
limit: {
type: Number,
default: 0,
},
width: {
type: Number,
default: 100,
},
height: {
type: Number,
default: 100,
},
// 前端删除: 点击删除icon不调取接口
frontDelete: {
type: Boolean,
default: true,
},
// 上传的图片是否可裁剪
crop: Boolean,
cropImageWidth: {
type: Number,
default: 200,
},
cropImageHeight: {
type: Number,
default: 200,
},
},
computed: {
margin() {
return (index) => {
const rightMargin =
(index + 1) % this.rowCount === 0 ? 0 : this.marginRight;
return "0 " + rightMargin + "px 10px 0";
};
},
multiple() {
return !this.limit || this.limit > 1 || this.limit - this.fileList.length > 1;
},
fileList: {
get() {
return this.list;
},
set() {},
},
},
data() {
return {
// 上传可裁剪图片的弹窗
cropShow: false,
marginRight: 10,
rowCount: 4,
previewImageShow: false,
currentUrl: "",
};
},
methods: {
initPage() {
this.$Utils.domResize(
this.$refs[this.refName],
({ width }) => {
this.rowCount = parseInt(width / this.width);
},
(remove) => {
this.$once("hook:beforeDestroy", () => {
remove();
});
}
);
},
transferFile(file) {
return new Promise((resolve) => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
this.fileList.push({
url: URL.createObjectURL(file),
});
resolve();
};
});
},
onChange(e) {
const { files } = e.target;
let arr = [];
for (let i = 1; i <= files.length; i++) {
if(!this.limit || this.limit >= i + this.fileList.length ) arr.push(this.transferFile(files[i - 1]));
else {
this.$message.warning("最多可选" + this.limit + "张图片");
break;
};
}
Promise.all(arr).then(() => {
this.$emit("change", {
files: this.fileList.map((file) => {
return {
id: file.id || "",
url: file.url,
};
}),
type: "upload",
});
});
},
onClick({ e, index }) {
const el = e.target;
const isZoomIn = el.className.includes("plus");
const isDelete = el.className.includes("trash");
if (isDelete) {
// 单项删除: 无需调取接口直接前端删除
if (this.frontDelete) {
this.fileList.splice(index, 1);
this.$emit("change", {
files: this.fileList.map((file) => {
return {
id: file.id || "",
url: file.url,
};
}),
type: "delete",
});
} else {
// 单项删除: 调取接口, 需要一定的反应时间, 成功才删
this.$emit("change", {
files: this.fileList,
type: "delete",
handleDelete: () => {
this.fileList.splice(index, 1);
},
});
}
} else if (isZoomIn) {
this.currentUrl = this.fileList[index].url;
this.previewImageShow = true;
}
},
uploadCropImage() {
this.cropShow = true;
},
onCrop({ base64 }) {
this.cropShow = false;
this.fileList.push({
id: "",
url: base64,
});
this.$emit("change", {
files: this.fileList.map((file) => {
return {
id: file.id || "",
url: file.url,
};
}),
type: "upload",
});
},
},
mounted() {
this.initPage();
},
};
</script>
<style scoped lang="scss">
@import "@/assets/css/theme.scss";
@import "@/assets/css/transition.scss";
.file-list {
display: inline-block;
}
.plus-view {
box-shadow: 0 0 0 0 !important;
border: 1px dashed $light;
color: $light;
position: relative;
margin-bottom: 10px;
transition: all 0.2s ease-in-out;
cursor: pointer;
}
.plus-view.disabled {
color: $disabled_border !important;
border-color: $disabled_border !important;
cursor: not-allowed !important;
}
.plus-view:hover {
border-color: $primary;
color: $primary;
}
.file-list,
.plus-view {
vertical-align: middle;
}
.file-item {
box-shadow: 0 2px 12px 0 $shadow_color;
border-radius: 4px;
display: inline-block;
position: relative;
overflow: hidden;
}
.file-item .image {
border: 0;
margin: 0;
width: 100%;
height: 100%;
object-fit: contain;
}
.icon-plus {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
}
.input {
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.file-wrap {
background: rgba(149, 144, 176, 0.5);
border-radius: 4px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
}
.fa-trash,
.fa-search-plus {
transition: all 0.2s ease-in-out;
}
.fa-trash:hover,
.fa-search-plus:hover {
transform: scale(1.2);
}
.icon-view {
width: 100%;
position: absolute;
top: 50%;
transform: translateY(-50%);
display: flex;
justify-content: space-around;
color: #fff;
font-size: 24px;
}
.icon-view .fa {
cursor: pointer;
}
.dialog /deep/.njx_card {
padding: 0;
border: 0;
}
.preview-image {
width: 100%;
object-fit: contain;
// display: block;
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
background: red;
}
::-webkit-scrollbar-thumb {
background: $primary;
border-radius: 4px;
}
</style>
tada~~没有放上dialog。。因为我来活儿了。。。。。。。把njx-dialog删了就能用。感谢阅读哈哈哈哈哈哈哈哈哈哈哈爱你爱你爱你
网友评论