123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- <template>
- <div class="cl-upload__wrap">
- <div
- class="cl-upload"
- :class="[
- `cl-upload--${listType}`,
- {
- 'is-multiple': multiple,
- 'is-drag': drag
- }
- ]"
- >
- <el-input class="cl-upload__hidden" type="hidden" v-model="value"></el-input>
- <el-upload
- :action="action"
- :accept="_accept"
- :multiple="multiple"
- :limit="limit"
- :data="data"
- :name="_name"
- :drag="drag"
- :list-type="listType"
- :file-list="fileList"
- :show-file-list="_showFileList"
- :auto-upload="autoUpload"
- :disabled="disabled"
- :headers="{
- Authorization: token,
- ...headers
- }"
- :http-request="action ? undefined : httpRequest"
- :on-remove="_onRemove"
- :on-preview="_onPreview"
- :on-success="_onSuccess"
- :on-error="onError"
- :on-progress="onProgress"
- :on-change="onChange"
- :on-exceed="onExceed"
- :before-upload="_beforeUpload"
- :before-remove="beforeRemove"
- :style="_style"
- v-loading="_loading"
- >
- <slot>
- <!-- picture-card -->
- <template v-if="listType == 'picture-card'">
- <i :class="['cl-upload__icon', _icon]"></i>
- <span class="cl-upload__text" v-if="_text">{{ _text }}</span>
- </template>
- <!-- text -->
- <template v-else-if="listType == 'text'">
- <el-button size="mini" type="primary" :loading="loading"
- >点击上传</el-button
- >
- </template>
- <template v-else>
- <div class="cl-upload__cover" v-if="_urls[0]">
- <img v-if="_urls[0].type == 'image'" :src="_urls[0].url" />
- <span v-else>{{ _urls[0].name }}</span>
- </div>
- <template v-else>
- <i :class="['cl-upload__icon', _icon]"></i>
- <span class="cl-upload__text" v-if="_text">{{ _text }}</span>
- </template>
- </template>
- </slot>
- </el-upload>
- </div>
- <cl-dialog
- title="图片预览"
- :visible.sync="preview.visible"
- :props="{
- width: previewWidth
- }"
- >
- <img width="100%" :src="preview.url" alt="" />
- </cl-dialog>
- </div>
- </template>
- <script>
- import { mapGetters } from "vuex";
- import path from "path";
- import { cloneDeep, last, isArray, isNumber, isEmpty } from "cl-admin/utils";
- import { v4 as uuidv4 } from "uuid";
- import { isDev } from "@/config/env";
- export default {
- name: "cl-upload",
- props: {
- value: [Array, String],
- // 尺寸
- size: [Array, String, Number],
- // 显示图标
- icon: String,
- // 显示文案
- text: String,
- // 是否以 uuid 重命名
- rename: {
- type: Boolean,
- default: true
- },
- // 最大允许上传文件大小(MB)
- limitSize: {
- type: Number,
- default: 2
- },
- // 预览图片的宽度
- previewWidth: {
- type: String,
- default: "500px"
- },
- // 上传的地址
- action: {
- type: String,
- default: ""
- },
- // 设置上传的请求头部
- headers: Object,
- // 是否多选
- multiple: Boolean,
- // 上传时附带的额外参数
- data: Object,
- // 上传的文件字段名
- name: String,
- // 支持发送 cookie 凭证信息
- withCredentials: Boolean,
- // 是否显示已上传文件列表
- showFileList: {
- type: Boolean,
- default: undefined
- },
- // 是否拖拽
- drag: Boolean,
- // 上传的文件类型
- accept: String,
- // 文件列表的类型
- listType: {
- type: String,
- default: "default"
- },
- // 是否自带上传
- autoUpload: {
- type: Boolean,
- default: true
- },
- // 是否禁用
- disabled: Boolean,
- // 最大允许上传个数
- limit: Number,
- // 文件列表移除文件时的钩子
- onRemove: Function,
- // 点击文件列表中已上传的文件时的钩子
- onPreview: Function,
- // 文件上传成功时的钩子
- onSuccess: Function,
- // 文件上传失败时的钩子
- onError: Function,
- // 文件上传时的钩子
- onProgress: Function,
- // 文件状态改变时的钩子
- onChange: Function,
- // 文件超出个数限制时的钩子
- onExceed: Function,
- // 上传文件之前的钩子
- beforeUpload: Function,
- // 删除文件之前的钩子
- beforeRemove: Function
- },
- data() {
- return {
- fileList: [],
- urls: [],
- loading: false,
- preview: {
- visible: false,
- url: null
- }
- };
- },
- computed: {
- ...mapGetters(["token", "modules", "upload"]),
- conf() {
- return this.modules.upload.options;
- },
- _size() {
- return this.size || this.conf.size || "128px";
- },
- _icon() {
- return this.icon || this.conf.icon || "el-icon-upload";
- },
- _text() {
- return this.text || this.conf.text || "选择文件";
- },
- _accept() {
- let d = this.accept || this.conf.accept;
- switch (this.listType) {
- case "picture-card":
- return d || "image/*";
- default:
- return d;
- }
- },
- _name() {
- return this.name || this.conf.name || "file";
- },
- _limitSize() {
- return this.limitSize || this.conf.limitSize || 2;
- },
- _showFileList() {
- let f = null;
- switch (this.listType) {
- case "picture-card":
- case "text":
- f = true;
- break;
- default:
- f = false;
- break;
- }
- return this.showFileList === undefined ? f : this.showFileList;
- },
- _loading() {
- return this.listType == "default" ? this.loading : false;
- },
- _urls() {
- const format = {
- image: ["bmp", "jpg", "jpeg", "png", "tif", "gif", "svg"]
- };
- return this.urls
- .filter((e) => Boolean(e.url))
- .map((e) => {
- let arr = e.url.split(".");
- let suf = last(arr);
- e.type = format.image.includes(suf) ? "image" : null;
- return e;
- });
- },
- _style() {
- let arr = [];
- if (isArray(this._size)) {
- arr = this._size;
- } else {
- arr = [this._size, this._size];
- }
- const [height, width] = arr.map((e) => (isNumber(e) ? `${e}px` : e));
- if (this.listType == "default" && !this.drag) {
- return {
- height,
- width
- };
- } else {
- return {};
- }
- }
- },
- watch: {
- value: {
- immediate: true,
- handler: "parseValue"
- }
- },
- methods: {
- // 解析参数
- parseValue(value) {
- let list = [];
- if (this.multiple) {
- if (value instanceof Array) {
- list = value;
- } else {
- list = (value || "").split(",");
- }
- } else {
- if (value) {
- list = [value];
- }
- }
- // 比较数据,避免重复动画
- if (
- !this.urls.some((e) => {
- return list.includes(e.url);
- })
- ) {
- this.fileList = list.filter(Boolean).map((url) => {
- return {
- url,
- name: path.basename(url),
- uid: url
- };
- });
- // 设置 URLS
- this.urls = cloneDeep(this.fileList);
- }
- },
- // 更新值
- update() {
- const urls = this.urls
- .filter((e) => Boolean(e.url))
- .map((e) => e.url)
- .join(",");
- this.$emit("input", urls);
- this.$emit("change", urls);
- },
- // 追加文件
- append(data) {
- if (this.multiple) {
- this.urls.push(data);
- } else {
- this.urls = [data];
- }
- this.update();
- },
- // 关闭上传加载中
- done() {
- this.loading = false;
- },
- // 删除文件
- _onRemove(file) {
- this.urls.splice(
- this.urls.findIndex((e) => e.uid === file.uid),
- 1
- );
- this.update();
- // 删除文件之前的钩子
- if (this.onRemove) {
- this.onRemove(file, this.urls);
- }
- },
- // 预览图片
- _onPreview(file) {
- this.preview.visible = true;
- this.preview.url = file.url;
- if (!file.url) {
- let item = this.urls.find((e) => e.uid == file.uid);
- if (item) {
- this.preview.url = item.url;
- }
- }
- },
- // 上传前
- _beforeUpload(file) {
- this.loading = true;
- if (this._limitSize) {
- if (file.size / 1024 / 1024 >= this._limitSize) {
- this.$message.error(`上传文件大小不能超过 ${this._limitSize}MB!`);
- this.done();
- return false;
- }
- }
- if (this.beforeUpload) {
- return this.beforeUpload(file, {
- done: this.done
- });
- }
- return true;
- },
- // 上传成功
- _onSuccess(res, file) {
- this.loading = false;
- this.append({
- url: res.data,
- name: file.raw.name,
- uid: file.raw.uid
- });
- // 文件上传成功时的钩子
- if (this.onSuccess) {
- this.onSuccess(res, file.raw, this.urls);
- }
- },
- // 重设上传请求
- httpRequest(req) {
- const isRename = isEmpty(this.rename) ? this.conf.rename : this.rename;
- const { mode } = this.upload;
- // 多种上传请求
- const upload = (file) => {
- return new Promise((resolve, reject) => {
- const next = (res) => {
- let data = new FormData();
- for (let i in res) {
- if (i != "host") {
- data.append(i, res[i]);
- }
- }
- let fileName = file.name;
- // 是否以 uuid 重新命名
- if (isRename) {
- fileName = uuidv4() + "." + last((file.name || "").split("."));
- }
- data.append("key", `app/${fileName}`);
- data.append("file", file);
- // 上传
- this.$service.common
- .request({
- url: res.host,
- method: "POST",
- headers: {
- "Content-Type": "multipart/form-data"
- },
- data,
- onUploadProgress: (e) => {
- if (this.onProgress) {
- e.percent = parseInt((e.loaded / e.total) * 100);
- this.onProgress(e, req.file);
- }
- }
- })
- .then((res) => {
- if (mode === "local") {
- resolve(res);
- } else {
- resolve(`${res.host}/app/${fileName}`);
- }
- })
- .catch((err) => {
- reject(err);
- });
- };
- if (mode == "local") {
- next({
- host: "/upload"
- });
- } else {
- this.$service.common
- .upload()
- .then((res) => {
- if (isDev) {
- res.host = "@/oss-upload";
- }
- next(res);
- })
- .catch(reject);
- }
- });
- };
- this.loading = true;
- upload(req.file)
- .then((url) => {
- this._onSuccess({ data: url }, { raw: req.file });
- })
- .catch((err) => {
- console.error("upload error", err);
- this.$message.error(err);
- // 文件上传失败时的钩子
- if (this.onError) {
- this.onError(err, req.file);
- }
- })
- .done(() => {
- this.loading = false;
- });
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .cl-upload {
- display: flex;
- flex-wrap: wrap;
- &__hidden {
- height: 0;
- width: 0;
- }
- &.is-multiple {
- .cl-upload__wrap {
- margin-right: 10px;
- }
- }
- &--default {
- &:not(.is-drag) {
- /deep/.el-upload {
- display: flex;
- align-items: center;
- justify-content: center;
- border: 1px dashed #d9d9d9;
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- height: 100%;
- &:hover {
- border-color: #409eff;
- }
- .cl-upload__cover {
- img {
- display: block;
- height: 100%;
- width: 100%;
- }
- }
- i {
- font-size: 28px;
- color: #8c939d;
- }
- }
- }
- }
- &--picture-card {
- /deep/.el-upload {
- .cl-upload__icon {
- position: relative;
- top: 4px;
- }
- }
- }
- &__icon + span {
- margin-left: 5px;
- }
- &__text {
- font-size: 14px;
- }
- }
- </style>
|