<template>
	<view class="z-table">
		<view class="z-table-main" :style="compluteHeight">
			<view v-if="!tableLoaded && (!tableData || !columns)" :class="['z-loading', {ztableLoading: tableShow}]">
				<view class="z-loading-animate"></view>
			</view>
			<view class="z-table-container">
				<view class="z-table-pack">
					<view class="z-table-title">
						<view class="z-table-title-item" :class="{ 'z-table-stick-side': stickSide && index == 0 }" :style="{ width: item.width ? item.width + 'rpx' : '200rpx' }"
						 v-for="(item, index) in columns" :key="index" @click="sort(item.key, index)">
							<view v-if="showSelect && !singleSelect && index === 0" class="select-box" @click="doSelect(true)">
								<view :class="['select-tip', {'selected': selectAll}]"></view>
							</view>
							<view :class="['z-table-col-text', {'text-left': titleTextAlign === 'left', 'text-center': titleTextAlign === 'center', 'text-right': titleTextAlign === 'right'}]">
								<view v-html="getTitleText(item.title)"></view>
								<view v-if="item.hasOwnProperty('key') && item.hasOwnProperty('sort') && tableData.length" class="sort">
									<view class="up-arrow" :class="{ action: nowSortKey == item.key && sortType == 'asc' }"></view>
									<view class="down-arrow" :class="{ action: nowSortKey == item.key && sortType == 'desc' }"></view>
								</view>
							</view>
						</view>
					</view>
					<view v-if="tableData.length" :class="['table-container-box', {'short-table': !longTable && showBottomSum}]">
						<view class="z-table-container-row" :class="{ 'z-table-has-bottom': showBottomSum }" v-for="(row, iIndex) in tableData"
						 :key="iIndex">
							<view :class="['z-table-container-col', { 'z-table-stick-side': stickSide && jIndex == 0 }]" :style="{ width: col.width ? col.width + 'rpx' : '200rpx' }"
							 v-for="(col, jIndex) in columns" :key="jIndex" @click="itemClick(row, col)">
								<view v-if="showSelect && jIndex === 0" class="select-box" @click="doSelect(false, iIndex)">
									<view :class="['select-tip', {'selected': selectArr.includes(iIndex)}]"></view>
								</view>
								<view :class="['z-table-col-text', {'text-left': textAlign === 'left', 'text-center': textAlign === 'center', 'text-right': textAlign === 'right'}]">
									<view v-if="!col.isLink" v-html="getRowContent(row, col)">
										<!-- <view v-if="!col.render" v-html="getRowContent(row, col)"></view> -->
										<!-- <renderComponents v-else :row="row" :col="col" /> -->
									</view>
									<!-- #ifdef H5 -->
									<router-link v-else-if="setUrl(row, col).indexOf('http') != 0" :to="setUrl(row, col)" v-html="getRowContent(row, col)"></router-link>
									<a v-else-if="col.isLink" :href="setUrl(row, col)" v-html="getRowContent(row, col)"></a>
									<!-- #endif -->
									<!-- #ifndef H5 -->
									<navigator v-else-if="col.isLink" :url="setUrl(row, col)" v-html="getRowContent(row, col)"></navigator>
									<!-- #endif -->
								</view>
							</view>
						</view>
					</view>
					<view :class="['z-table-bottom', {'long-table': longTable}]" v-if="showBottomSum && tableData.length">
						<view class="z-table-bottom-col" :class="{ 'z-table-stick-side': stickSide && sumIndex == 0 }" :style="{ width: sumCol.width ? sumCol.width + 'rpx' : '200rpx' }"
						 v-for="(sumCol, sumIndex) in columns" :key="sumIndex">
							<view class="z-table-bottom-text">
								<!-- <view v-if="sumIndex != 0" class="z-table-bottom-text-title">{{ sumCol.title }}</view> -->
								<text :class="{ sum: sumIndex == 0 }">{{ sumIndex == 0 ? '总计' : dosum(sumCol) }}</text>
							</view>
						</view>
					</view>
				</view>
			</view>
			<view v-if="tableData && tableData.length == 0 && !tableLoaded" class="table-empty">
				<!-- image v-if="!showLoading" class="empty-img" src="../static/empty.png"></image -->
				<view v-html="showLoading ? '' : emptyText"></view>
			</view>
		</view>
	</view>
</template>

<script>
	/*
	 * 表格使用
	 * 注意如果需要异步加载，需要把tableData初始值设为false，当没有数据的时候值为空数组
	 * props: tableData [Array | Boolean] | 表格数据 如果为false则显示loading
	 * 		 columns [Array | Boolean] | 数据映射表 如果为false则显示loading 每列params => title(表头文字可以是html字符串模版), width(每列宽度) [, key(对应tableData的字段名) || format(自定义内容), sort(是否要排序), isLink(是否显示为超链接Object)]
	 * 										   format格式: {template: 字符串模版用#key#表示需要被替换的数据,names: 对应template属性内要被替换的内容的key}
	 * 										   isLink格式: {url: 链接地址, params: 地址带的参数Array[key|value, key|value, ...]每一项都是key和value以'|'链接,如果不带'|'默认键值同名
	 * 										   listenerClick(是否监听点击事件Boolean)}
	 * 		 stickSide Boolean | 是否固定右侧首栏 默认不显示
	 * 		 showBottomSum Boolean | 是否显示底部统计 默认不显示
	 * 		 showLoading Boolean | 是否首次加载首次加载不显示暂无数据内容
	 * 		 emptyText String | 空数据显示的文字内容
	 *		 tableHeight Number | 设置表格高度会滚动
	 *		 sort Boolean | 开启排序
	 * 		 showSelect Boolean | 开启选择
	 *		 singleSelect Boolean | 在开启选择的状态下是否开起单选
	 * 		 textAlign String | 内容对齐方式 left center right
	 * 		 titleTextAlign String | 表头对齐方式 left center right
	 *
	 * event: onSort | 排序事件 返回{key: 被排序列的字段名, type: 正序'asc'/倒序'desc'}
	 *		  onSelect | 选中时触发 返回选择的行的下标
	 * 		  onClick | 单元格点击事件 返回点击单元格所属行的数据
	 *
	 * function: resetSort | 调用后重置排序 *注意:不会触发sort事件
	 *
	 * */
	import Vue from 'vue'
	// import tableRender from './table-render'

	export default {
		data() {
			return {
				version: '1.1.3',
				nowSortKey: '',
				sortType: 'desc', // asc/desc 升序/降序
				longTable: true,
				lineHeight: uni.upx2px(64),
				tableLoaded: false,
				tableShow: true,
				selectAll: false,
				selectArr: []
			}
		},
		// mixin: [tableRender],
		computed: {
			compluteHeight() {
				return this.tableHeight ?
					'height: ' + uni.upx2px(this.tableHeight) + 'px' :
					''
			}
		},
		props: {
			tableData: {
				type: [Array, Boolean],
				default () {
					return false
				}
			},
			columns: {
				/*
				 *
				 * [{title: xxx, key: 当前列展示对象名, width: 列宽, render: function}]
				 *
				 * */
				type: [Array, Boolean],
				required: true
			},
			stickSide: {
				type: Boolean,
				default: false
			},
			showBottomSum: {
				type: Boolean,
				default: false
			},
			showLoading: {
				type: Boolean,
				default: true
			},
			emptyText: {
				type: String,
				default: '暂无数据'
			},
			tableHeight: {
				type: [Number, Boolean],
				default: 0
			},
			showSelect: {
				type: Boolean,
				default: false
			},
			singleSelect: {
				type: Boolean,
				default: false
			},
			textAlign: {
				type: String,
				default: 'left' // right|center|left
			},
			titleTextAlign: {
				type: String,
				default: 'left' // right|center|left
			}
		},
		mounted() {
			this.init()
		},
		// components: {
		// 	renderComponents: {
		// 		functional: true,
		// 		props: {
		// 			row: {
		// 				type: Object,
		// 				required: true
		// 			},
		// 			col: {
		// 				type: Object,
		// 				required: true
		// 			}
		// 		},
		// 		render: function(h, ctx) {
		// 			return _this[ctx.props.col.render](h, ctx.props)
		// 		}
		// 	}
		// },
		watch: {
			columns() {
				this.init()
			},
			tableData() {
				this.init()
			}
		},
		methods: {
			async init() {
				// 重置选择内容
				this.selectAll = false
				this.selectArr = []
				this.tableLoaded = false
				this.tableShow = true
				let _this = this
				let container = await _this.getPageSize('.z-table-container'),
					pack = await _this.getPageSize('.z-table-pack')
				_this.timer && clearTimeout(_this.timer)
				if (container && pack) {
					_this.$nextTick(function() {
						if (_this.tableData && _this.tableData.length) {
							_this.tableShow = false
							_this.timer = setTimeout(function() {
								_this.tableLoaded = true
							}, 300)
						}
					})
					if (container.height != pack.height) {
						_this.longTable = true
					} else {
						_this.longTable = false
					}
				} else {
					_this.tableLoaded = false
					_this.$nextTick(function() {
						_this.tableShow = true
					})
				}
			},
			getPageSize(selecter) {
				// 获取元素信息
				let query = uni.createSelectorQuery().in(this),
					_this = this
				return new Promise((resolve, reject) => {
					query
						.select(selecter)
						.boundingClientRect(res => {
							resolve(res)
						})
						.exec()
				})
			},
			dosum({key, noSum = false, formatNum = true}) {
				let sum = '-'
				if (noSum) return sum
				if (this.tableData) {
					if (
						this.tableData.every(item => {
							return !Number.isNaN(item[key] - 0)
						})
					) {
						sum = 0
						this.tableData.map((item, index) => {
							if (!key && index != 0) {
								sum = '-'
							} else {
								let val = item[key] - 0
								if (Number.isNaN(val)) {
									sum += 0
								} else {
									sum += val
								}
							}
						})
					}
				}
				// sum = sum == 0 ? "-" : sum
				return formatNum ? this.numTransform(sum) : sum
			},
			getRowContent(row, col) {
				// 表格值处理函数
				// 如果columns带了key则显示对应的key
				// 如果columns带的format则按规定返回format后的html
				// format规定: params names <Array> 对应tableData的键名,作为匹配template中两个#之间动态内容的名字
				//			   params template <String> html字符串模版
				let tempHTML = ''
				let rowKey = row[col.key]
				if ([null, ''].includes(rowKey)) {
					rowKey = '-'
				}
				let { formatNum = true } = col
				if (rowKey || rowKey === 0) {
					tempHTML = isNaN(rowKey - 0) || !formatNum ?
						rowKey :
						this.numTransform(rowKey - 0)
					// tempHTML = tempHTML == 0 ? "-" : tempHTML
				} else if (!!col.format) {
					let tempFormat = col.format.template
					col.format.names.map(item => {
						let regexp = new RegExp(`\#${item}\#`, 'mg')
						tempFormat = tempFormat.replace(regexp, row[item])
					})
					tempHTML = tempFormat
				} else if (!col.render) {
					let error = new Error('数据的key或format值至少一个不为空')
					throw error
				}
				// console.log(tempHTML)
				return tempHTML.toString()
			},
			sort(key, index) {
				if (!key || !this.columns[index].sort) {
					return
				}
				// 排序功能: 如果点击的排序按钮是原先的 那么更改排序类型
				//			如果点击的另一个排序按钮 那么选择当前排序并且排序类型改为降序(desc)
				if (key != this.nowSortKey) {
					this.nowSortKey = key
					this.sortType = 'desc'
				} else {
					this.toggleSort()
				}
				this.$emit('onSort', {
					key: this.nowSortKey,
					type: this.sortType
				})
			},
			toggleSort() {
				this.sortType = this.sortType == 'asc' ? 'desc' : 'asc'
			},
			numTransform(n) {
				if (Number.isNaN(n - 0)) {
					return n
				}
				if (Math.abs(n) >= 100000000) {
					n = Number((n / 100000000).toFixed(1)) + '亿'
				} else if (Math.abs(n) >= 10000) {
					n = Number((n / 10000).toFixed(1)) + '万'
				}
				return n.toString()
			},
			resetSort() {
				// 重置排序状态
				this.nowSortKey = ''
				this.sortType = 'desc'
			},
			setUrl(row, col) {
				if (!col.isLink) {
					return
				}
				let urlParam = {}
				let {
					isLink: {
						url,
						params = []
					}
				} = col
				params.forEach(item => {
					if (~item.indexOf('|')) {
						let temp = item.split('|')
						urlParam[temp[0]] = row[temp[1]]
					} else {
						urlParam[item] = row[item]
					}
				})
				url = this.setUrlParams(url, urlParam)
				return url
			},
			setUrlParams(url, params) {
				let tempUrl = url,
					keyArr = Object.keys(params)
				keyArr.forEach(item => {
					tempUrl += `&${item}=${params[item]}`
				})
				tempUrl = tempUrl.replace(/\&/, '?')
				return tempUrl
			},
			itemClick(row, col) {
				if (col.listenerClick) {
					this.$emit('onClick', row)
				}
			},
			doSelect(isAll = false, index) {
				let temp = new Set()
				if (isAll) {
					// 全选
					if (!this.selectAll) {
						for (let i = 0; i < this.tableData.length; i++) {
							temp.add(i)
						}
					}
				} else {
					// if (!this.singleSelect) {
					// 	this.selectArr.forEach(item => {
					// 		temp.add(item)
					// 	})
					// }
					this.selectArr.forEach(item => {
						temp.add(item)
					})
					if (temp.has(index)) {
						temp.delete(index)
					} else {
						if (this.singleSelect) {
							temp.clear()
						}
						temp.add(index)
					}
				}
				this.selectArr = Array.from(temp)
				// console.log(this.selectArr)
				if (this.selectArr.length == this.tableData.length) {
					this.selectAll = true
				} else {
					this.selectAll = false
				}
				
				this.$emit('onSelect', this.selectArr)
			},
			// 1.1.1
			getTitleText(title) {
				// 自定义表头
				let tempHTML = title
				return tempHTML.toString()
			}
		}
	}
</script>

<style lang="scss">
	.navigator-hover {
		background: transparent;
		opacity: 1;
	}

	@mixin ellipsis($num: 1) {
		overflow: hidden;
		text-overflow: ellipsis;

		@if $num==1 {
			white-space: nowrap;
		}

		@else {
			display: -webkit-box;
			-webkit-line-clamp: $num;
			/* autoprefixer: off */
			-webkit-box-orient: vertical;
			/* autoprefixer: on */
		}
	}

	// 三角形
	%triangle-basic {
		content: '';
		height: 0;
		width: 0;
		overflow: hidden;
	}

	@mixin triangle($direction, $size, $borderColor) {
		@extend %triangle-basic;

		@if $direction==top {
			border-bottom: $size solid $borderColor;
			border-left: $size dashed transparent;
			border-right: $size dashed transparent;
			border-top: 0;
		}

		@else if $direction==right {
			border-left: $size solid $borderColor;
			border-top: $size dashed transparent;
			border-bottom: $size dashed transparent;
			border-right: 0;
		}

		@else if $direction==bottom {
			border-top: $size solid $borderColor;
			border-left: $size dashed transparent;
			border-right: $size dashed transparent;
			border-bottom: 0;
		}

		@else if $direction==left {
			border-right: $size solid $borderColor;
			border-top: $size dashed transparent;
			border-bottom: $size dashed transparent;
			border-left: 0;
		}
	}

	a {
		text-decoration: none;
	}

	.z-table {
		position: relative;
		display: inline-block;
		height: 100%;
		min-height: 130rpx;
		width: 100%;
		background: #fff;
		border: solid 2rpx #ccc;
		font-size: $uni-font-size-sm;
		box-sizing: border-box;
		transform: translateZ(0);

		.z-table-main {
			height: 100%;
			box-sizing: border-box;
		}

		.z-table-container {
			height: 100%;
			overflow: scroll;
			box-sizing: border-box;
		}

		.z-table-pack {
			position: relative;
			min-height: 100%;
			width: fit-content;
		}

		.z-table-title {
			position: sticky;
			top: 0;
			height: 64rpx;
			z-index: 1;

			.z-table-title-item {
				border-bottom: solid 1rpx #dbdbdb;
				background: #f8f8f8;
			}

			.z-table-stick-side {
				position: sticky;
				top: 0;
				left: 0;
				border-right: solid 1rpx #dbdbdb;
				box-sizing: border-box;
			}
		}

		.table-container-box.short-table {
			padding-bottom: 48rpx;
		}

		.z-table-title,
		.z-table-container-row {
			display: flex;
			width: fit-content;
			white-space: nowrap;
			box-sizing: border-box;

			.z-table-title-item,
			.z-table-container-col {
				@include ellipsis();
				display: inline-flex;
				padding: 0 16rpx;
				height: 64rpx;
				align-items: center;
				line-height: 64rpx;
				box-sizing: border-box;
			}
		}

		.z-table-container-row {
			z-index: 0;
			border-bottom: solid 1rpx #f4f4f4;
			box-sizing: border-box;
		}

		.z-table-stick-side {
			position: sticky;
			left: 0;
			background: #f7f9ff;
			border-right: solid 1rpx #dbdbdb;
			box-sizing: border-box;
		}

		.z-table-bottom {
			position: absolute;
			bottom: 0;
			z-index: 9;
			display: flex;
			justify-items: center;
			width: fit-content;
			background: #4298f7 !important;
			color: #fff !important;
			white-space: nowrap;
			box-sizing: border-box;

			&.long-table {
				position: sticky;
			}

			.z-table-stick-side {
				background: #4298f7 !important;
				box-sizing: border-box;
			}

			.z-table-bottom-col {
				display: inline-flex;
				align-items: center;
				text-align: center;
				padding: 16rpx;
				box-sizing: border-box;
			}

			.z-table-bottom-text {
				line-height: 100%;
				box-sizing: border-box;
			}

			.z-table-bottom-text-title {
				margin-bottom: 10rpx;
				font-size: 22rpx;
				color: #aad0ff;
				box-sizing: border-box;
			}

			.sum {
				margin-left: 14rpx;
				font-size: 28rpx;
				box-sizing: border-box;
			}
		}

		.table-empty {
			position: absolute;
			top: 64rpx;
			height: 64rpx;
			line-height: 64rpx;
			width: 100%;
			text-align: center;
		}

		.sort {
			display: flex;
			padding: 5rpx;
			flex-direction: column;
			justify-content: center;

			.up-arrow {
				@include triangle(top, 10rpx, #ccc);
				display: block;
				margin-bottom: 5rpx;

				&.action {
					@include triangle(top, 10rpx, #4298f7);
				}
			}

			.down-arrow {
				@include triangle(bottom, 10rpx, #ccc);
				display: block;

				&.action {
					@include triangle(bottom, 10rpx, #4298f7);
				}
			}
		}

		// 1.0.5
		.z-loading {
			position: absolute;
			top: 0;
			left: 0;
			z-index: 2;
			display: flex;
			align-items: center;
			justify-content: center;
			height: 100%;
			width: 100%;
			background: #fff;
			opacity: 0;
			transition: all 0.3s;

			&.ztableLoading {
				opacity: 1;
			}

			.z-loading-animate {
				position: relative;
				display: inline-block;
				width: 30rpx;
				height: 30rpx;
				margin-right: 20rpx;
				border-radius: 100%;
				border: solid 6rpx #ccc;
				vertical-align: middle;
				animation: rotate 1s ease-in-out infinite;

				&::after {
					content: '';
					display: block;
					position: absolute;
					top: -10rpx;
					z-index: 1;
					background: #fff;
					width: 20rpx;
					height: 20rpx;
					border-radius: 10rpx;
				}
			}

			@keyframes rotate {
				from {
					transform: rotate(0deg);
				}

				to {
					transform: rotate(360deg);
				}
			}
		}

		// 1.1.0
		.select-box {
			display: inline-block;
			width: 26rpx;
			height: 26rpx;
			line-height: 14rpx;
			margin-right: 15rpx;
			border: solid 2rpx #4298f7;
			border-radius: 4rpx;
			background: #fff;
			text-align: center;
		}

		.select-tip {
			display: inline-block;
			opacity: 0;
			transform: rotate(90deg);
			transition: all .3s;

			&.selected {
				position: relative;
				top: 4rpx;
				left: -4rpx;
				height: 4rpx;
				background: #4298f7;
				width: 10rpx;
				opacity: 1;
				transform: rotate(45deg);

				&:before,
				&:after {
					content: '';
					position: absolute;
					display: block;
					height: 4rpx;
					background: #4298f7;
				}

				&:before {
					bottom: -2rpx;
					left: -4rpx;
					width: 8rpx;
					transform: rotate(-90deg);
				}

				&:after {
					bottom: 16rpx;
					right: -16rpx;
					width: 34rpx;
					transform: rotate(-90deg);
				}
			}
		}
		
		// 1.1.1
		.z-table-col-text {
			display: flex;
			width: 100%;
			flex: 1;
			justify-content: flex-start;
			align-content: center;
			
			&.text-center {
				justify-content: center;
			}
			
			&.text-right {
				justify-content: flex-end;
			}
		}
	}
</style>
