<template>
	<div class="system-task">
		<el-row class="box scroller1" type="flex" :gutter="10">
			<!-- 系统，用户自定义，已停止 -->
			<el-col v-for="(item, index) in list" :key="index">
				<div class="block" :class="[`_${item.key}`]">
					<div class="header">
						<!-- 图标 -->
						<i class="icon" :class="item.icon"></i>
						<!-- 标题 -->
						<span class="label">{{ item.label }}</span>
						<!-- 数量 -->
						<span class="num">({{ item.pagination.total }})</span>
						<span class="flex1"></span>
						<!-- 操作按钮 -->
						<ul class="op-btn">
							<li v-if="item.loading">
								<i class="el-icon-loading"></i>
							</li>

							<li
								v-else
								@click="refreshTask({ page: 1 })"
								class="refresh-btn"
								v-permission="perm.delete"
							>
								<i class="el-icon-refresh"></i>
								<span>刷新</span>
							</li>

							<li @click="edit(item.params)" class="add-btn" v-permission="perm.add">
								<i class="el-icon-plus"></i>
								<span>添加</span>
							</li>
						</ul>
					</div>

					<div class="container scroller1">
						<draggable
							v-model="list[index].list"
							v-bind="drag.options"
							tag="ul"
							:data-type="item.params.type"
							:data-status="item.params.status"
							@end="(e) => onDragEnd(e, item)"
						>
							<li
								v-for="item2 in list[index].list"
								:key="item2.id"
								:data-id="item2.id"
								@contextmenu.stop.prevent="openCM($event, item2)"
								class="_drag"
							>
								<div class="h">
									<span class="type _warning" v-show="item2.status === 0">
										{{ item2.type | task_type }}
									</span>
									<span class="name">{{ item2.name }}</span>
								</div>

								<div class="remark">{{ item2.remark }}</div>

								<div class="f">
									<template v-if="item2.status">
										<span class="date">{{ item2.nextRunTime || "..." }}</span>
										<span class="start">进行中</span>
									</template>

									<template v-else>
										<span>...</span>
										<span class="stop">已停止</span>
									</template>
								</div>

								<el-row type="flex" class="op">
									<el-col v-if="item2.status === 0" @click.native="start(item2)">
										<i class="el-icon-video-play"></i>
										<span>开始</span>
									</el-col>

									<el-col
										v-else
										@click.native="stop(item2)"
										v-permission="perm.stop"
									>
										<i class="el-icon-video-pause"></i>
										<span>暂停</span>
									</el-col>

									<el-col
										@click.native="edit(item2)"
										v-permission="{
											and: [perm.update, perm.info]
										}"
									>
										<i class="el-icon-edit"></i>
										<span>编辑</span>
									</el-col>

									<el-col @click.native="findLog(item2)" v-permission="perm.log">
										<i class="el-icon-tickets"></i>
										<span>查看日志</span>
									</el-col>
								</el-row>
							</li>

							<li v-if="list[index].list.length == 0">
								<div class="empty">暂无数据</div>
							</li>
						</draggable>

						<el-button
							class="more"
							type="text"
							size="mini"
							@click="moreTask(index, item)"
							v-if="item.pagination.total >= item.pagination.size"
							>查看更多</el-button
						>
					</div>

					<div class="footer">
						<button class="btn-add" @click="edit(item.params)" v-permission="perm.add">
							<i class="el-icon-plus"></i>
						</button>
					</div>
				</div>
			</el-col>

			<!-- 日志 -->
			<el-col v-permission="perm.log">
				<div class="block _log">
					<div class="header">
						<!-- 标题 -->
						<span class="label">日志</span>
						<!-- 数量 -->
						<span class="num">({{ logs.pagination.total }})</span>
						<span class="flex1"></span>

						<!-- 是否异常 -->
						<el-checkbox-group
							class="status"
							v-model="logs.filters.status"
							@change="filterLog"
						>
							<el-checkbox :label="0">异常</el-checkbox>
						</el-checkbox-group>

						<!-- 操作按钮 -->
						<ul class="op-btn">
							<li @click="refreshLog({ page: 1 })">
								<i class="el-icon-refresh"></i>
								<span>刷新</span>
							</li>

							<li v-if="logs.current" class="_current-log" @click="allLog">
								<span>{{ logs.current.name }}</span>
								<i class="el-icon-close"></i>
							</li>
						</ul>
					</div>

					<div
						class="container"
						v-loading="logs.loading"
						element-loading-text="拼命加载中"
					>
						<ul class="scroller1" v-infinite-scroll="moreLog">
							<li
								v-for="(item, index) in logs.list"
								:key="index"
								:class="{ _error: item.status == 0 }"
								@click="expandLog(item)"
							>
								<div class="h">
									<span class="name">{{ index + 1 }} · {{ item.taskName }}</span>
								</div>

								<div class="remark" :class="{ _ellipsis: !item._expand }">
									{{ item.detail || "..." }}
								</div>

								<div class="f">
									<span>执行时间：{{ item.createTime }}</span>
								</div>
							</li>

							<li v-if="logs.list.length == 0">
								<div class="empty">暂无数据</div>
							</li>
						</ul>
					</div>
				</div>
			</el-col>
		</el-row>
	</div>
</template>

<script>
import draggable from "vuedraggable";
import { checkPerm } from "cool/components/base";
import { Form, ContextMenu } from "cl-admin-crud";

export default {
	name: "system-task",

	components: {
		draggable
	},

	data() {
		return {
			list: [
				{
					key: "sys",
					label: "系统任务",
					icon: "el-icon-s-tools",
					list: [],
					loading: false,
					params: {
						type: 0,
						status: 1
					},
					pagination: {
						size: 10,
						page: 1,
						total: 0
					}
				},
				{
					key: "user",
					label: "用户自定义任务",
					icon: "el-icon-user-solid",
					list: [],
					loading: false,
					params: {
						type: 1,
						status: 1
					},
					pagination: {
						size: 10,
						page: 1,
						total: 0
					}
				},
				{
					key: "stop",
					label: "已停止任务",
					list: [],
					loading: false,
					params: {
						type: null,
						status: 0
					},
					pagination: {
						size: 10,
						page: 1,
						total: 0
					}
				}
			],
			logs: {
				loading: false,
				list: [],
				pagination: {
					size: 10,
					page: 1
				},
				params: {},
				filters: {
					status: []
				},
				current: null
			},
			drag: {
				options: {
					group: "Task",
					animation: 300,
					ghostClass: "Ghost",
					dragClass: "Drag",
					draggable: "._drag"
				}
			}
		};
	},

	filters: {
		task_type(i) {
			return i === 0 ? "系统" : "用户";
		}
	},

	mounted() {
		this.refreshTask({ page: 1 });
	},

	computed: {
		perm() {
			return this.$service.system.task.permission;
		}
	},

	methods: {
		// 任务拖动
		onDragEnd({ to, item }) {
			const status = to.getAttribute("data-status");
			const type = to.getAttribute("data-type");
			const id = item.getAttribute("data-id");

			if (status == 0) {
				this.stop({ id });
			}

			if (status == 1) {
				this.start({ id, type });
			}
		},

		// 右键菜单
		openCM(e, { id, status, type, name }) {
			let menus = [
				{
					label: "立即执行",
					perm: ["once"],
					"suffix-icon": "el-icon-video-play",
					callback: (e, close) => {
						this.once({ id });
						close();
					}
				},
				{
					label: "编辑",
					perm: ["update", "info"],
					"suffix-icon": "el-icon-edit",
					callback: (e, close) => {
						this.edit({ id, type });
						close();
					}
				},
				{
					label: "删除",
					perm: ["delete"],
					"suffix-icon": "el-icon-delete",
					callback: (e, close) => {
						this.delete({ id });
						close();
					}
				},
				{
					label: "查看日志",
					perm: ["log"],
					"suffix-icon": "el-icon-tickets",
					callback: (e, close) => {
						this.findLog({ id, name });
						close();
					}
				}
			];

			if (status == 1) {
				menus.splice(1, 0, {
					label: "暂停",
					perm: ["stop"],
					"suffix-icon": "el-icon-video-pause",
					callback: (e, close) => {
						this.stop({ id, type });
						close();
					}
				});
			} else {
				menus.splice(1, 0, {
					label: "开始",
					perm: ["start"],
					"suffix-icon": "el-icon-video-play",
					callback: (e, close) => {
						this.start({ id, type });
						close();
					}
				});
			}

			ContextMenu.open(e, {
				list: menus.filter((e) => {
					return checkPerm({
						and: e.perm.map((a) => this.perm[a])
					});
				})
			});

			return false;
		},

		// 编辑任务
		async edit(params) {
			const { id, type } = params || {};

			let info = {
				type
			};

			if (id) {
				info = await this.$service.system.task.info({ id });
			}

			if (info.every) {
				info.every /= 1000;
			}

			if (!info.limit) {
				info.limit = undefined;
			}

			const { setForm } = Form.open({
				title: `编辑任务`,
				width: "600px",
				props: {
					"label-width": "80px"
				},
				items: [
					{
						label: "名称",
						prop: "name",
						value: info.name,
						component: {
							name: "el-input",
							attrs: {
								placeholder: "请输入名称"
							}
						},
						rules: {
							required: true,
							message: "名称不能为空"
						}
					},
					{
						label: "类型",
						prop: "taskType",
						value: info.taskType || 0,
						component: {
							name: "el-select",
							options: [
								{
									label: "cron",
									value: 0
								},
								{
									label: "时间间隔",
									value: 1
								}
							],
							on: {
								change: (v) => {
									if (v == 0) {
										setForm("limit", undefined);
										setForm("every", undefined);
									} else {
										setForm("cron", undefined);
									}
								}
							}
						}
					},
					{
						label: "cron",
						prop: "cron",
						hidden: ({ scope }) => {
							return scope.taskType == 1;
						},
						value: info.cron,
						component: {
							name: "el-input",
							attrs: {
								placeholder: "* * * * * *"
							}
						},
						rules: {
							required: true,
							message: "cron不能为空"
						}
					},
					{
						label: "次数",
						prop: "limit",
						value: info.limit,
						hidden: ({ scope }) => {
							return scope.taskType == 0;
						},
						component: {
							name: "el-input-number",
							props: {
								min: 1,
								max: 10000
							}
						}
					},
					{
						label: "间隔(秒)",
						prop: "every",
						value: info.every,
						hidden: ({ scope }) => {
							return scope.taskType == 0;
						},
						component: {
							name: "el-input-number",
							props: {
								min: 1,
								max: 100000000
							}
						},
						rules: {
							required: true,
							message: "执行间隔不能为空"
						}
					},
					{
						label: "service",
						prop: "service",
						value: info.service,
						component: {
							name: "el-input",
							attrs: {
								placeholder: "sys.test.add(params)"
							}
						}
					},
					{
						label: "开始时间",
						prop: "startDate",
						value: info.startDate,
						component: {
							name: "el-date-picker",
							props: {
								type: "datetime"
							}
						}
					},
					{
						label: "结束时间",
						prop: "endDate",
						value: info.endDate,
						component: {
							name: "el-date-picker",
							props: {
								type: "datetime"
							}
						}
					},
					{
						label: "备注",
						prop: "remark",
						value: info.remark,
						component: {
							name: "el-input",
							props: {
								type: "textarea"
							}
						}
					},
					{
						label: "状态",
						prop: "status",
						value: info.status === 0 ? 0 : 1,
						component: {
							name: "el-radio-group",
							options: [
								{
									label: "停止",
									value: 0
								},
								{
									label: "运行",
									value: 1
								}
							]
						}
					}
				],
				on: {
					submit: (data, { close, done }) => {
						if (!data.limit) {
							data.limit = null;
						}

						this.$service.system.task[id ? "update" : "add"]({
							...info,
							...data,
							every: data.every * 1000
						})
							.then(() => {
								this.refreshTask();

								this.$message.success("保存成功");
								close();
							})
							.catch((err) => {
								this.$message.error(err);
								done();
							});
					}
				}
			});
		},

		// 删除任务
		delete({ id }) {
			this.$confirm("此操作将永久删除该任务，是否继续？", "提示", {
				type: "warning"
			})
				.then(() => {
					this.$service.system.task.delete({ ids: id }).then(() => {
						this.refreshTask();
					});
				})
				.catch(() => {});
		},

		// 开始任务
		start({ id, type }) {
			this.$service.system.task
				.start({ id, type })
				.then(() => {
					this.refreshTask();
				})
				.catch((err) => {
					this.$message.error(err);
				});
		},

		// 停止任务
		stop({ id }) {
			this.$service.system.task
				.stop({ id })
				.then(() => {
					this.refreshTask();
				})
				.catch((err) => {
					this.$message.error(err);
				});
		},

		// 任务执行一次
		once({ id }) {
			this.$service.system.task
				.once({ id })
				.then(() => {
					this.refreshTask();
				})
				.catch((err) => {
					this.$message.error(err);
				});
		},

		expandLog(e) {
			this.$set(e, "_expand", !e._expand);
		},

		// 刷新任务
		refreshTask(params, options) {
			const { index, more } = options || {};
			const arr =
				index === undefined || index === null ? this.list.map((e, i) => i) : [index];

			arr.forEach(async (k) => {
				let item = this.list[k];

				Object.assign(item.params, {
					...item.pagination,
					...params
				});

				this.$set(item, "loading", true);

				let res = await this.$service.system.task.page(item.params);

				this.moreList(res, item);

				if (!more) {
					this.$el.querySelector(`.block._${item.key} .container`).scroll(0, 0);
				}

				item.loading = false;
			});
		},

		// 更多任务
		moreTask(index) {
			this.refreshTask(null, { index, more: true });
		},

		// 刷新日志
		async refreshLog(newParams, options) {
			if (this.logs.loading) {
				return false;
			}

			if (!checkPerm(this.perm.log)) {
				return false;
			}

			const { params, pagination } = this.logs;
			const { more } = options || {};

			Object.assign(params, {
				...pagination,
				...newParams
			});

			this.logs.loading = true;

			let res = await this.$service.system.task.log(params);

			this.moreList(res, this.logs);

			if (!more) {
				this.$el.querySelector(".block._log .container ul").scroll(0, 0);
			}

			this.logs.loading = false;
		},

		// 更多日志
		moreLog() {
			this.refreshLog(null, { more: true });
		},

		// 查看任务对应的日志
		findLog(e) {
			this.logs.current = e;
			this.refreshLog({ page: 1, id: e.id });
		},

		// 所有日志
		allLog() {
			this.logs.current = null;
			this.refreshLog({ page: 1, id: null });
		},

		// 过滤日志
		filterLog([v]) {
			this.refreshLog({ page: 1, status: v === undefined ? 1 : 0 });
		},

		moreList(res, { list, pagination }) {
			const { page, size } = res.pagination;
			const len = res.list.length;
			const max = list.length;

			if (page == 1) {
				list.splice(0, max, ...res.list);
			} else {
				let start = max - (max % size);
				let end = start + len;

				list.splice(start, end, ...res.list);
			}

			if (len == size) {
				res.pagination.page += 1;
			}

			Object.assign(pagination, res.pagination);

			return page != 1;
		}
	}
};
</script>

<style lang="scss" scoped>
.Ghost {
	opacity: 0.7;
}

.Drag {
	border: 1px dashed #000 !important;
	background: #fff !important;
}

.system-task {
	.box {
		height: 100%;
		overflow-x: auto;

		.el-col {
			height: 100%;
			width: 413px;
		}
	}

	.block {
		height: 100%;
		width: 400px;

		.header {
			display: flex;
			align-items: center;
			height: 40px;
			background-color: #f0f0f0;
			border-top-left-radius: 10px;
			border-top-right-radius: 10px;
			position: relative;
			top: 5px;
			z-index: 1;
			padding: 0 10px 5px 10px;

			i {
				font-size: 18px;
			}

			.label {
				font-size: 12px;
				margin: 0 5px;
				letter-spacing: 0.5px;
			}

			.num {
				font-size: 12px;
			}

			.flex1 {
				flex: 1;
			}

			.op-btn {
				display: flex;

				li {
					display: flex;
					align-items: center;
					list-style: none;
					cursor: pointer;
					padding: 2px 10px;
					background-color: #fff;
					border-radius: 3px;
					margin-left: 5px;

					&:hover {
						background-color: #dedede;
						color: #444;
					}

					i {
						font-size: 13px;
						margin-right: 2px;
					}

					span {
						font-size: 12px;
					}
				}
			}
		}

		.container {
			max-height: calc(100% - 90px);
			overflow-y: auto;
			margin-bottom: 5px;
			z-index: 2;
			position: relative;
			background-color: #f7f7f7;

			ul {
				li {
					list-style: none;
					background-color: #fff;
					border-radius: 5px;
					margin-bottom: 5px;
					padding: 10px 15px;
					font-size: 14px;
					letter-spacing: 0.5px;
					border: 1px solid #f7f7f7;

					&:last-child {
						margin-bottom: 0;
					}

					&._drag {
						cursor: pointer;
					}

					&:hover {
						.op {
							height: 30px;
						}
					}

					.h {
						display: flex;
						align-items: center;
						font-size: 14px;
						margin-bottom: 10px;

						.type {
							font-size: 12px;
							border-radius: 3px;
							padding: 1px 2px;
							margin-right: 5px;

							&._warning {
								background-color: $color-warning;
								color: #fff;
							}
						}
					}

					.remark {
						font-size: 12px;
						color: #666;
						margin-bottom: 20px;
					}

					.empty {
						text-align: center;
						font-size: 13px;
						color: #666;
						margin: 10px 0;
					}

					.f {
						display: flex;
						align-items: center;
						justify-content: space-between;
						position: relative;

						.date {
							font-size: 12px;
							color: #fff;
							background-color: $color-primary;
							border-radius: 2px;
							margin-left: 40px;
							padding: 1px 3px;

							&::before {
								content: "NEXT";
								position: absolute;
								left: 0;
								top: 1px;
								color: #222;
							}
						}

						.start,
						.stop {
							display: flex;
							align-items: center;
							font-size: 12px;
							margin-left: 30px;
							position: relative;

							&::before {
								content: "";
								display: block;
								height: 6px;
								width: 6px;
								border-radius: 6px;
								position: absolute;
								left: -15px;
							}
						}

						.start {
							color: #67c23a;

							&::before {
								background-color: #67c23a;
							}
						}

						.stop {
							color: #f56c6c;

							&::before {
								background-color: #f56c6c;
							}
						}
					}

					.op {
						height: 0;
						margin-top: 15px;
						transition: height 0.3s;
						overflow: hidden;

						.el-col {
							height: 30px;
							display: flex;
							justify-content: center;
							align-items: center;

							&:hover {
								background-color: #f7f7f7;
							}

							span {
								font-size: 12px;
								color: #666;
							}

							i {
								font-size: 16px;
								margin-right: 5px;
							}
						}
					}

					&._error {
						background-color: $color-danger;
						color: #fff;

						.remark {
							color: #fff !important;
						}
					}
				}
			}

			.more {
				display: block;
				margin: 10px auto;
			}
		}

		.footer {
			height: 36px;

			.btn-add {
				height: 34px;
				width: 100%;
				border-radius: 3px;
				border: 0;
				background-color: #fff;
				cursor: pointer;

				i {
					font-size: 16px;
					color: #999;
				}

				&:hover {
					box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
				}
			}
		}
	}

	.block._stop {
		.header {
			.add-btn {
				display: none;
			}
		}

		.container {
			max-height: calc(100% - 50px);
		}

		.footer {
			display: none;
		}
	}

	.block._log {
		.header {
			.status {
				.el-checkbox {
					margin-right: 10px;
				}
			}

			.op-btn {
				li {
					display: flex;
					align-items: center;
					height: 20px;

					&._current-log {
						span {
							display: block;
							max-width: 100px;
							overflow: hidden;
							text-overflow: ellipsis;
							white-space: nowrap;
						}

						i {
							margin-left: 2px;
						}

						&:hover {
							i {
								color: $color-danger;
							}
						}
					}
				}
			}
		}

		.container {
			height: calc(100% - 50px);
			max-height: calc(100% - 50px);

			ul {
				height: 100%;

				li {
					.remark {
						color: #999;

						&._ellipsis {
							overflow: hidden;
							text-overflow: ellipsis;
							display: -webkit-box;
							-webkit-box-orient: vertical;
							-webkit-line-clamp: 2;
						}
					}

					.f {
						font-size: 12px;
					}

					&:hover {
						.remark {
							color: #444;
						}
					}
				}
			}
		}
	}
}
</style>
