uni-col.vue 7.5 KB
<template>
	<!-- #ifndef APP-NVUE -->
	<view :class="['uni-col', sizeClass, pointClassList]" :style="{
		paddingLeft:`${Number(gutter)}rpx`,
		paddingRight:`${Number(gutter)}rpx`,
	}">
		<slot></slot>
	</view>
	<!-- #endif -->
	<!-- #ifdef APP-NVUE -->
	<!-- nvue上,类名样式不生效,换为style -->
	<!-- 设置right正值失效,设置 left 负值 -->
	<view :class="['uni-col']" :style="{
		paddingLeft:`${Number(gutter)}rpx`,
		paddingRight:`${Number(gutter)}rpx`,
		width:`${nvueWidth}rpx`,
		position:'relative',
		marginLeft:`${marginLeft}rpx`,
		left:`${right === 0 ? left : -right}rpx`
	}">
		<slot></slot>
	</view>
	<!-- #endif -->
</template>

<script>
	/**
	 * Col	布局-列
	 * @description	搭配uni-row使用,构建布局。
	 * @tutorial	https://ext.dcloud.net.cn/plugin?id=3958
	 *
	 * @property	{span} type = Number 栅格占据的列数
	 * 						默认 24
	 * @property	{offset} type = Number 栅格左侧的间隔格数
	 * @property	{push} type = Number 栅格向右移动格数
	 * @property	{pull} type = Number 栅格向左移动格数
	 * @property	{xs} type = [Number, Object] <768px 响应式栅格数或者栅格属性对象
	 * 						@description	Number时表示在此屏幕宽度下,栅格占据的列数。Object时可配置多个描述{span: 4, offset: 4}
	 * @property	{sm} type = [Number, Object] ≥768px 响应式栅格数或者栅格属性对象
	 * 						@description	Number时表示在此屏幕宽度下,栅格占据的列数。Object时可配置多个描述{span: 4, offset: 4}
	 * @property	{md} type = [Number, Object] ≥992px 响应式栅格数或者栅格属性对象
	 * 						@description	Number时表示在此屏幕宽度下,栅格占据的列数。Object时可配置多个描述{span: 4, offset: 4}
	 * @property	{lg} type = [Number, Object] ≥1200px 响应式栅格数或者栅格属性对象
	 * 						@description	Number时表示在此屏幕宽度下,栅格占据的列数。Object时可配置多个描述{span: 4, offset: 4}
	 * @property	{xl} type = [Number, Object] ≥1920px 响应式栅格数或者栅格属性对象
	 * 						@description	Number时表示在此屏幕宽度下,栅格占据的列数。Object时可配置多个描述{span: 4, offset: 4}
	 */
	const ComponentClass = 'uni-col';

	// -1 默认值,因为在微信小程序端只给Number会有默认值0
	export default {
		name: 'uniCol',
		// #ifdef MP-WEIXIN
		options: {
			virtualHost: true // 在微信小程序中将组件节点渲染为虚拟节点,更加接近Vue组件的表现
		},
		// #endif
		props: {
			span: {
				type: Number,
				default: 24
			},
			offset: {
				type: Number,
				default: -1
			},
			pull: {
				type: Number,
				default: -1
			},
			push: {
				type: Number,
				default: -1
			},
			xs: [Number, Object],
			sm: [Number, Object],
			md: [Number, Object],
			lg: [Number, Object],
			xl: [Number, Object]
		},
		data() {
			return {
				gutter: 0,
				sizeClass: '',
				parentWidth: 0,
				nvueWidth: 0,
				marginLeft: 0,
				right: 0,
				left: 0
			}
		},
		created() {
			// 字节小程序中,在computed中读取$parent为undefined
			let parent = this.$parent;

			while (parent && parent.$options.componentName !== 'uniRow') {
				parent = parent.$parent;
			}

			this.updateGutter(parent.gutter)
			parent.$watch('gutter', (gutter) => {
				this.updateGutter(gutter)
			})

			// #ifdef APP-NVUE
			this.updateNvueWidth(parent.width)
			parent.$watch('width', (width) => {
				this.updateNvueWidth(width)
			})
			// #endif
		},
		computed: {
			sizeList() {
				let {
					span,
					offset,
					pull,
					push
				} = this;

				return {
					span,
					offset,
					pull,
					push
				}
			},
			// #ifndef APP-NVUE
			pointClassList() {
				let classList = [];

				['xs', 'sm', 'md', 'lg', 'xl'].forEach(point => {
					const props = this[point];
					if (typeof props === 'number') {
						classList.push(`${ComponentClass}-${point}-${props}`)
					} else if (typeof props === 'object' && props) {
						Object.keys(props).forEach(pointProp => {
							classList.push(
								pointProp === 'span' ?
								`${ComponentClass}-${point}-${props[pointProp]}` :
								`${ComponentClass}-${point}-${pointProp}-${props[pointProp]}`
							)
						})
					}
				});

				// 支付宝小程序使用 :class=[ ['a','b'] ],渲染错误
				return classList.join(' ');
			}
			// #endif
		},
		methods: {
			updateGutter(parentGutter) {
				parentGutter = Number(parentGutter);
				if (!isNaN(parentGutter)) {
					this.gutter = parentGutter / 2
				}
			},
			// #ifdef APP-NVUE
			updateNvueWidth(width) {
				// 用于在nvue端,span,offset,pull,push的计算
				this.parentWidth = width;
				['span', 'offset', 'pull', 'push'].forEach(size => {
					const curSize = this[size];
					if ((curSize || curSize === 0) && curSize !== -1) {
						let RPX = 1 / 24 * curSize * width
						RPX = Number(RPX);
						switch (size) {
							case 'span':
								this.nvueWidth = RPX
								break;
							case 'offset':
								this.marginLeft = RPX
								break;
							case 'pull':
								this.right = RPX
								break;
							case 'push':
								this.left = RPX
								break;
						}
					}
				});
			}
			// #endif
		},
		watch: {
			sizeList: {
				immediate: true,
				handler(newVal) {
					// #ifndef APP-NVUE
					let classList = [];
					for (let size in newVal) {
						const curSize = newVal[size];
						if ((curSize || curSize === 0) && curSize !== -1) {
							classList.push(
								size === 'span' ?
								`${ComponentClass}-${curSize}` :
								`${ComponentClass}-${size}-${curSize}`
							)
						}
					}
					// 支付宝小程序使用 :class=[ ['a','b'] ],渲染错误
					this.sizeClass = classList.join(' ');
					// #endif
					// #ifdef APP-NVUE
					this.updateNvueWidth(this.parentWidth);
					// #endif
				}
			}
		}
	}
</script>

<style lang='scss' >
	/* breakpoints */
	$--sm: 768px !default;
	$--md: 992px !default;
	$--lg: 1200px !default;
	$--xl: 1920px !default;

	$breakpoints: ('xs' : (max-width: $--sm - 1),
	'sm' : (min-width: $--sm),
	'md' : (min-width: $--md),
	'lg' : (min-width: $--lg),
	'xl' : (min-width: $--xl));

	$layout-namespace: ".uni-";
	$col: $layout-namespace+"col";

	@function getSize($size) {
		/* TODO 1/24 * $size * 100 * 1%; 使用计算后的值,为了解决 vue3 控制台报错 */
		@return 0.04166666666 * $size * 100 * 1%;
	}

	@mixin res($key, $map:$breakpoints) {
		@if map-has-key($map, $key) {
			@media screen and #{inspect(map-get($map,$key))} {
				@content;
			}
		}

		@else {
			@warn "Undeinfed point: `#{$key}`";
		}
	}

	/* #ifndef APP-NVUE */
	#{$col} {
		float: left;
		box-sizing: border-box;
	}

	#{$col}-0 {
		/* #ifdef APP-NVUE */
		width: 0;
		height: 0;
		margin-top: 0;
		margin-right: 0;
		margin-bottom: 0;
		margin-left: 0;
		/* #endif */
		/* #ifndef APP-NVUE */
		display: none;
		/* #endif */
	}

	@for $i from 0 through 24 {
		#{$col}-#{$i} {
			width: getSize($i);
		}

		#{$col}-offset-#{$i} {
			margin-left: getSize($i);
		}

		#{$col}-pull-#{$i} {
			position: relative;
			right: getSize($i);
		}

		#{$col}-push-#{$i} {
			position: relative;
			left: getSize($i);
		}
	}

	@each $point in map-keys($breakpoints) {
		@include res($point) {
			#{$col}-#{$point}-0 {
				display: none;
			}

			@for $i from 0 through 24 {
				#{$col}-#{$point}-#{$i} {
					width: getSize($i);
				}

				#{$col}-#{$point}-offset-#{$i} {
					margin-left: getSize($i);
				}

				#{$col}-#{$point}-pull-#{$i} {
					position: relative;
					right: getSize($i);
				}

				#{$col}-#{$point}-push-#{$i} {
					position: relative;
					left: getSize($i);
				}
			}
		}
	}

	/* #endif */
</style>