<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>
