vue2组件化开发(顶部导航、左侧导航等、根据宽度展开隐藏标签行)持续更新

把一些通用一些的代码抽成组件

1.顶部导航

实现内容:根据传参,定义导航栏显示的内容,并适配移动端
最终实现
pc(基本功能实现了):
先组建一个默认的ui表单,然后根据父组件传参判断是否要使用这个默认的ui,根据sort字段可删除组件中的内容,删除后可以进行排序(如果排序中不存在sort字段会默认往最后排)
在这里插入图片描述
代码分析:

父组件调用

//传参内容我写在子组件了
<Top
			fontColor="red"
			fontSize="18px"
			fontFamily="宋体"
			topHeight="80px"
			homeWidth="800px"
			:addNavListLeft="[
				{ name: '测试99', toLink: '' },
				{ name: '测试88', toLink: '' },
			]"
			:addNavListRight="[
				{ name: '测试1', toLink: '' },
				{ name: '测试2', toLink: '' },
			]"
			:navListLeftDefalut="true"
			:navListRightDefalut="true"
			:databaseDefalut="true"
			:defalutSort="[6, 2, 1, 4, 5, 3]"
			:deleteSort="[1, 2]"
			:alginRight="true"
		></Top>

子组件调用

  1. 参数含义
	props: [
		'fontColor', //字体颜色
		'fontSize', //字体大小
		'fontFamily', //字体
		'homeWidth',//组件整体宽度
		'topHeight',//组件整体高度
		'addNavListLeft', //增加左侧navlist
		'addNavListRight', //增加右侧navlist
		'navListLeftDefalut', //左侧list是否使用默认值
		'navListRightDefalut', //右侧list是否使用默认值
		'databaseDefalut', //特色内容是否使用
		'defalutSort', //默认排序数组sort字段,因为执行过删除之后,数组的长度就变了,
		//拼起来的最终数组的length的长度也变了,后面的sort也变,
		//所以可能导致排序结果不理想
		//(目前做法是如果在这个sort中没有出现总数组中sort字段的内容,就默认根据数字从小到大往后排)
		'deleteSort', //根据sort字段删除数组中的某些值,删除只能删除默认值,不能超出默认值的length
		'alginRight', //整体居右的情况()
	],
  1. template部分
<template>
	<div>
		<div v-if="isMobile">
			<!-- 左上角悬浮导航按钮 -->
			<button class="drawer-button" @click.stop="toggleDrawer"></button>

			<!-- 左侧悬浮抽屉导航 -->
			<aside :class="{ 'show-drawer': isDrawerOpen }" @click.stop>
				<!-- 导航链接 -->
				<ul>
					<li><a href="#">Home</a></li>
					<li><a href="#">About</a></li>
					<li><a href="#">Services</a></li>
					<!-- 其他链接 -->
				</ul>
			</aside>
		</div>
		<div
			class="toplogin"
			v-else
			:style="{ height: topHeight ? topHeight : '' }"
		>
			<div
				class="home clearfix"
				v-if="!alginRight"
				:style="{ width: homeWidth ? homeWidth : '' }"
			>
				<ul class="nav-top fl clearfix">
					<slot name="nav-top-content-l"></slot>
					<!-- <top>
       在父组件中使用子组件的插槽 
      <template v-slot:nav-top-content-l>
        <p>这是父组件中使用的内容。</p>
      </template>
      </top>-->
					<li class="fl" v-show="isShowLogin">
						<!-- 默认登录 -->
						<a :href="loginUrl" class="nav_a" @click="login"
							>登录</a
						>
					</li>
					<!-- 默认退出登录 -->
					<li class="fl" v-show="isShowName">
						<a href="javascript:;" class="nav_a">{{
							'欢迎您,' + userName
						}}</a>
						<a href="javascript:;" class="nav_a" @click="loginOut"
							>退出</a
						>
					</li>
					<!-- 默认内容循环 -->
					<li
						class="fl"
						v-for="(item, index) in navButtonListLeft"
						:key="index"
					>
						<!-- color优先数组内的color 其次是父组件的fontcolor 最后是组件内的color -->
						<a
							:href="item.toLink"
							target="_blank"
							:style="{
								color: item.color
									? item.color
									: fontColor
									? fontColor
									: '',
								fontSize: fontSize ? fontSize : '',
								fontFamily: fontFamily ? fontFamily : '',
							}"
							>{{ item.name }}</a
						>
					</li>
				</ul>
				<ul class="nav-top fr clearfix">
					<slot name="nav-top-content-r"></slot>
					<li class="fl">
						<a href="" target="_blank"
							></a
						>
					</li>
					<li class="fl">
						<a></a>
					</li>
				</ul>
			</div>
			<div
				class="home clearfix"
				v-else
				:style="{ width: homeWidth ? homeWidth : '' }"
			>
				<ul class="nav-top fr clearfix">
					<slot name="nav-top-content-align-r"></slot>
					<!-- <top>
				       在父组件中使用子组件的插槽 
				      <template v-slot:nav-top-content-l>
				        <p>这是父组件中使用的内容。</p>
				      </template>
				      </top>-->
					<li class="fl" v-show="isShowLogin">
						<!-- 默认登录 -->
						<a :href="loginUrl" class="nav_a" @click="login"
							>登录</a
						>
					</li>
					<!-- 默认退出登录 -->
					<li class="fl" v-show="isShowName">
						<a href="javascript:;" class="nav_a">{{
							 userName
						}}</a>
						<a href="javascript:;" class="nav_a" @click="loginOut"
							>退出</a
						>
					</li>
					<!-- 默认内容循环 -->
					<li
						class="fl"
						v-for="(item, index) in navButtonListLeft"
						:key="index"
					>
						<!-- color优先数组内的color 其次是父组件的fontcolor 最后是组件内的color -->
						<a
							:href="item.toLink"
							target="_blank"
							:style="{
								color: item.color
									? item.color
									: fontColor
									? fontColor
									: '',
								fontSize: fontSize ? fontSize : '',
								fontFamily: fontFamily ? fontFamily : '',
							}"
							>{{ item.name }}</a
						>
					</li>
				</ul>
			</div>
		</div>
	</div>
</template>

  1. script部分
export default {
	props: [
		'fontColor', //字体颜色
		'fontSize', //字体大小
		'fontFamily', //字体
		'homeWidth',
		'topHeight',
		'addNavListLeft', //增加左侧navlist
		'addNavListRight', //增加右侧navlist
		'navListLeftDefalut', //左侧list是否使用默认值
		'navListRightDefalut', //右侧list是否使用默认值
		'databaseDefalut', //特色内容是否使用
		'defalutSort', //默认排序数组sort字段,因为执行过删除之后,数组的长度就变了,拼起来的最终数组的length的长度也变了,后面的sort也变,所以可能导致排序结果不理想(目前做法是如果在这个sort中没有出现总数组中sort字段的内容,就默认根据数字从小到大往后排)
		'deleteSort', //根据sort字段删除数组中的某些值,删除只能删除默认值,不能超出默认值的length
		'alginRight', //整体居右的情况()
	],
	//传入 字体颜色 字体大小 字体 / 左侧navlist 右侧navlist list是否使用默认值 特色内容是否使用
	//默认传入的和默认值共存
	//登录使用默认方法,如果需要修改请使用插槽
	//样式推荐在自己的样式表改,穿透一下

	data() {
		return {
			url: [''],
			uid: '',
			loginUrl: '',
			isShowLogin: true,
			isShowName: false,
			userName: '',
			//判断 手机端情况
			isMobile:
				window.innerWidth <= 768 ||
				/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
					navigator.userAgent
				),
			//手机端左侧导航抽屉
			isDrawerOpen: false,
			//目前默认数组里面有6个值
			navButtonListLeft: [
				{
					name: '测试1',
					toLink: '',
					sort: 1,
					color: '',
				},
				{
					name: '测试2',
					toLink: '',
					sort: 2,
					color: '',
				},
				{
					name: '测试3',
					toLink: '',
					sort: 3,
					color: '',
				},
				{ name: '测试4', toLink: '', sort: 4 },
				{
					name: '测试5',
					toLink: '',
					sort: 5,
					color: '',
				},
				{
					name: '测试6',
					toLink: '',
					sort: 6,
					color: '',
				},
			],
			navButtonListRight: [
				{
					name: '测测',
					toLink: '',
					sort: 1,
					color: '',
				},
			],
		};
	},
	watch: {
		$props: {
			handler(to) {
				// console.log(to);
			},
			immediate: true,
		},
	},
	created() {
		this.uid = this.$route.query.uid;
		this.logenter(this.fontColor);
	},
	mounted() {
		let propsParam = this.$props;
		//第一步,判断是否使用左侧数组
		if (propsParam.navListLeftDefalut) {
			//如果使用
			//第二部,拼接数组
			if (propsParam.addNavListLeft) {
				for (
					let index = 0;
					index < propsParam.addNavListLeft.length;
					index++
				) {
					const element = propsParam.addNavListLeft[index];
					this.navButtonListLeft.push({
						name: element.name,
						toLink: element.toLink,
						sort: this.navButtonListLeft.length + 1,
					});
				}
			}
			//第三步,执行删除
			if (propsParam.deleteSort) {
				this.navButtonListLeft = this.navButtonListLeft.filter(
					(item) => !propsParam.deleteSort.includes(item.sort)
				);
			}
			//第四步,执行排序
			if (propsParam.defalutSort) {
				this.navButtonListLeft.sort((a, b) => {
					const sortA = propsParam.defalutSort.indexOf(a.sort);
					const sortB = propsParam.defalutSort.indexOf(b.sort);
					// 如果 a.sort 或 b.sort 不在 defalutSort 中,则放到末尾
					if (sortA === -1) return 1;
					if (sortB === -1) return -1;
					return sortA - sortB; // 正常排序
				});
			}
		} else {
			//如果不用默认数组 直接替换掉
			this.navButtonListLeft = propsParam.addNavListLeft;
		}
	},
	methods: {
		login() {
			//登录
		},
		logenter() {
			//登录
		},
		loginOut() {
			//登出逻辑
		},
		toggleDrawer() {
			this.isDrawerOpen = !this.isDrawerOpen;
			if (this.isDrawerOpen) {
				document.addEventListener('click', this.closeDrawer);
			} else {
				document.removeEventListener('click', this.closeDrawer);
			}
		},
		closeDrawer(event) {
			if (!this.$el.contains(event.target)) {
				this.isDrawerOpen = false;
				document.removeEventListener('click', this.closeDrawer);
			}
		},
	},
};
  1. style部分
.toplogin {
	height: 40px;
	background: #fff;
	line-height: 40px;
	color: #ccc;
	border-bottom: 1px solid #c9def3;
	.nav-top {
		li {
			a {
				border-right: 2px solid #ccc;
				font-size: 13px;
				color: #666;
				padding: 0 15px;
				text-decoration: none;
				font-family: 'Microsoft YaHei';
			}
			a:hover {
				color: #c00;
			}
		}
		li:last-child a {
			border-right: 0px solid #ccc;
		}
	}
}
.nav {
	height: 45px;
	line-height: 45px;
	background: #134b84;
	li {
		float: left;
		a {
			cursor: pointer;
			color: #fff;
			font-size: 16px;
			padding: 0px 25px;
		}
		a:hover {
			color: #ff3 !important;
		}
	}
	li:first-child a {
		padding-left: 0;
	}
	li:last-child a {
		padding-right: 0;
	}
	li:nth-child(8) a {
		cursor: default;
		color: #f5da45;
		font-size: 20px;
		font-weight: 700;
		padding: 0px 23px;
		padding-left: 60px;
	}
	li:nth-child(8) a:hover {
		color: #f5da45 !important;
	}
	li:nth-child(9) a {
		border-left: 2px solid #f5da45;
		color: #f5da45;
		padding: 0px 24px;
	}
	li:nth-child(10) a {
		color: #f5da45;
		padding-left: 24px;
	}
	li:nth-child(11) a {
		color: #f5da45;
		padding-left: 24px;
	}
}
.yellow {
	color: #ff3 !important;
}
.drawer-button {
	position: fixed;
	top: 20px;
	left: 20px;
	background: transparent;
	border: none;
	cursor: pointer;
	font-size: 24px;
	z-index: 99999; /* 更高的层级 */
}

/* 左侧悬浮抽屉导航样式 */
aside {
	position: fixed;
	top: 0;
	left: 0;
	width: 250px;
	height: 100%;
	background-color: #fff;
	box-shadow: 2px 0 5px rgba(0, 0, 0, 0.2);
	transition: transform 0.3s ease;
	transform: translateX(-250px); /* 隐藏抽屉 */
	z-index: 9999; /* 更高的层级 */
}

aside.show-drawer {
	transform: translateX(0); /* 显示抽屉 */
}

/* 导航链接样式 */
aside ul {
	list-style: none;
	padding: 20px;
}

aside ul li {
	margin-bottom: 15px;
}

aside ul li a {
	text-decoration: none;
	color: #333;
	font-weight: bold;
}

移动(还在开发中):
在这里插入图片描述

2.左侧导航nav

实现内容:
组件思想,传一整个完整的title和下面的子内容 不是传一整个导航,如果一整个导航有多个类别就多调用几次,
组件中涉及到的icon全部使用插槽模式,暂时没涉及到使用不同的icon内容
后期需要优化内容:
1.增加更多的样式内容
2.改为整个导航一起打印的-难点 拼数据,迭代,每级目录循环
3.插槽内容-icon个性化(每个位置传不同的样式)
4.目前只有三级,后续可以加入完全迭代的代码
5.数据格式优化
6.类名优化
PC:
在这里插入图片描述

父组件调用

		<leftCloumn
			class="bbbb"
			eventBusId="bustype1"
			:titleStyles="{
				fontColor: 'blue',
				fontSize: '18',
				fontFamily: '',
				iconStyle: {
					type: 'image',
					content: './',
				},
				titleBackground: {
					type: 'image',
					content: './images/titlebg.png',
				},
			}"
			navWidth="320"
			navMinHeight="300"
			:customContent="assemblyList"
			:setMenuItemLevle1Styles="{
				activeColor: 'yellow',
				fontColor: 'blue',
				fontSize: '18px',
				fontWeight: '500',
				fontFamily: '',
				iconStyle: {
					type: 'text',
					content: 'bbb',
				},
			}"
			:setMenuItemLevle2Styles="{
				activeColor: 'red',
				fontColor: 'blue',
				fontSize: '12px',
				fontWeight: '500',
				fontFamily: '',
				iconStyle: {
					type: 'text',
					content: 'bbb',
				},
			}"
			:setMenuItemLevle3Styles="{
				activeColor: 'green',
				fontColor: 'blue',
				fontSize: '12px',
				fontWeight: '500',
				fontFamily: '',
				iconStyle: {
					type: 'text',
					content: 'bbb',
				},
			}"
		>
			<template v-slot:nav-left-title-icon>
				<span>icon</span>
			</template>
			<template v-slot:nav-left-Levle1-icon>
				<span style="vertical-align: bottom">icon</span>
			</template>
			<template v-slot:nav-left-Levle2-icon>
				<span style="vertical-align: bottom">icon</span>
			</template>
			<template v-slot:nav-left-Levle3-icon>
				<span style="vertical-align: bottom">icon</span>
			</template></leftCloumn
		>
父组件数据
defalutValue: [
				{
					uid: '1',
					order_number: 1,
					title_name: 'aaa',
					id: 30,
					childrenList: [],
				},
				{
					uid: '2',
					order_number: 2,
					title_name: 'bbb',
					id: 31,
					childrenList: [
						{
							uid: '4',
							order_number: 1,
							title_name: '111',
							id: 24,
							childrenList: [
								{
									uid: '7',
									order_number: 1,
									title_name: '!!!',
									id: 24,
									childrenList: [
										{
											uid: '8',
											order_number: 1,
											title_name: '1a',
											id: 24,
											childrenList: [],
										},
										{
											uid: '9',
											order_number: 2,
											title_name: '2b',
											id: 26,
											childrenList: [],
										},
										{
											uid: '10',
											order_number: 4,
											title_name: '3c',
											id: 32,
											childrenList: [],
										},
									],
								},
								{
									uid: '8',
									order_number: 2,
									title_name: '@@@',
									id: 26,
									childrenList: [],
								},
								{
									uid: '9',
									order_number: 4,
									title_name: '###',
									id: 32,
									childrenList: [],
								},
							],
						},
						{
							uid: '5',
							order_number: 2,
							title_name: '222',
							id: 26,
							childrenList: [],
						},
						{
							uid: '6',
							order_number: 4,
							title_name: '333',
							id: 32,
							childrenList: [],
						},
					],
				},
				{
					uid: '3',
					order_number: 3,
					title_name: 'ccc',
					id: 25,
					childrenList: [],
				},
			]
父组件数据拼装
/**
		 *
		 * @param {*} data 传入的数据数组
		 * @param {*} level 当前是第几级
		 * @param {*} titleName title的名称
		 * @param {*} levelTitleStyles title样式
		 */
		assembly(data, titleName) {
			let newList = {
				titlename: titleName,
				dataList: [],
			};
			//1第一级循环
			for (let i = 0; i < data.length; i++) {
				//每级的数据
				const item = data[i];
				//存入的数据
				let newParams = {};
				//临时数组----------这里是需要根据获得的数据源改动-因为最终传入到组件中的数据必须按照规则
				//因为不能确定传过来的data中的字段名称就只能这样写了
				if (item.uid) {
					newParams.uid = item.uid;
				}
				if (item.order_number) {
					newParams.order_number = item.order_number;
				}
				if (item.title_name) {
					newParams.title_name = item.title_name;
				}
				if (item.id) {
					newParams.id = item.id;
				}
				if (item.childrenList) {
					newParams.childrenList = item.childrenList;
				}
				//---------------------------------------------------
				//最终数组
				newList.dataList.push({
					//数据
					dataList: newParams,
				});
				//第二级循环
				for (let j = 0; j < item.childrenList.length; j++) {
					const childItem = item.childrenList[j];

					//迭代查询,第三级循环
					if (childItem.childrenList.length > 0) {
						this.assembly(childItem.childrenList, '');
					}
				}
			}
			this.assemblyList = newList;
			// return newList;
		},
父组件eventbus
this.$eventBus.$on('bustype1', function (data) {
			//这个是匿名函数哦,不能直接用this
			console.log(data);
		});

子组件代码

<template>
	<!-- 导航 -->
	<!-- 导航整体是由一个最外层宽控制的,里面元素的宽没有控制,里面是用padding来控制的 -->
	<!-- 组件思想,传一整个完整的title和下面的子内容 不是传一整个导航 -->
	<!-- 如果一整个导航有多个类别就多调用几次 -->
	<!-- 组件中涉及到的icon全部使用插槽模式,暂时没涉及到使用不同的icon内容, -->
	<!-- 后期需要优化内容:1.增加更多的样式内容 
                        2.改为整个导航一起打印的-难点 拼数据,迭代,每级目录循环
                        3.插槽内容-icon个性化(每个位置传不同的样式)
                        4.目前只有三级,后续可以加入完全迭代的代码
                        5.数据格式优化
                        6.类名优化
  -->
	<!-- eventbus传递数据 -->
	<!-- <div class="column_l_nav_box" :style="setDivBackground"> -->
	<div class="column_l_nav_box">
		<slot name="nav-left-content-top"></slot>
		<!-- <top>
       在父组件中使用子组件的插槽 
      <template v-slot:nav-top-content-top>
        <p>这是父组件中使用的内容。</p>
      </template>
      </top>-->
		<h5 class="column_l_nav_title" :style="setTitleBackground">
			<slot name="nav-left-title-icon"></slot>
			<!-- <top>
       在父组件中使用子组件的插槽 
      <template v-slot:nav-top-content-top>
        <p>这是父组件中使用的内容。</p>
      </template>
      </top>-->
			{{ topTitle }}
		</h5>
		<el-menu
			:default-active="currentChoose"
			:unique-opened="true"
			class="el-menu-vertical-demo"
			@select="selectMenu"
			style="border-right: none"
		>
			<div v-for="(item, index) in navData" :key="index">
				<el-submenu
					v-if="item.childrenList.length > 0"
					:index="item.uid"
					class="column_l_nav_item"
				>
					<template slot="title">
						<span
							@click="
								selectMenu2(item.uid, item.title_name, item.id)
							"
							class="column_l_nav_style1"
							style="width: 100%; height: 100%; display: block"
							:style="`color: ${
								currentChoose == item.uid
									? setMenuItemLevle1Styles.activeColor
									: setMenuItemLevle1Styles.fontColor
									? setMenuItemLevle1Styles.fontColor
									: ''
							};
              font-size: ${
					setMenuItemLevle1Styles.fontSize
						? setMenuItemLevle1Styles.fontSize
						: '22px'
				};
              font-weight:${
					setMenuItemLevle1Styles.fontWeight
						? setMenuItemLevle1Styles.fontWeight
						: '500'
				};
        font-family:${
			setMenuItemLevle1Styles.fontFamily
				? setMenuItemLevle1Styles.fontFamily
				: ''
		}
        `"
							><slot name="nav-left-Levle1-icon"></slot>
							<!-- <top>
       在父组件中使用子组件的插槽 
      <template v-slot:nav-top-content-top>
        <p>这是父组件中使用的内容。</p>
      </template>
      </top>-->{{ item.title_name }}</span
						>
					</template>
					<el-menu-item-group>
						<el-menu-item
							v-if="sitem.childrenList.length <= 0"
							v-for="(sitem, i) in item.childrenList"
							:key="i"
							:index="sitem.uid + ''"
							class="column_l_nav_style2"
							:style="`color: ${
								currentChoose == sitem.uid
									? setMenuItemLevle2Styles.activeColor
									: setMenuItemLevle2Styles.fontColor
									? setMenuItemLevle2Styles.fontColor
									: ''
							};
              font-size: ${
					setMenuItemLevle2Styles.fontSize
						? setMenuItemLevle2Styles.fontSize
						: '22px'
				};
              font-weight:${
					setMenuItemLevle2Styles.fontWeight
						? setMenuItemLevle2Styles.fontWeight
						: '500'
				};
        font-family:${
			setMenuItemLevle2Styles.fontFamily
				? setMenuItemLevle2Styles.fontFamily
				: ''
		}
        `"
							><slot name="nav-left-Levle2-icon"></slot>
							<!-- <top>
       在父组件中使用子组件的插槽 
      <template v-slot:nav-top-content-top>
        <p>这是父组件中使用的内容。</p>
      </template>
      </top>-->{{ sitem.title_name }}
						</el-menu-item>

						<el-submenu
							v-if="sitem.childrenList.length > 0"
							v-for="(sitem, i) in item.childrenList"
							:key="i"
							:index="sitem.uid + ''"
						>
							<template slot="title">
								<span
									@click="
										selectMenu2(
											sitem.uid,
											sitem.title_name,
											sitem.id
										)
									"
									style="
										width: 100%;
										height: 100%;
										display: block;
									"
									:style="`color: ${
										currentChoose == sitem.uid
											? setMenuItemLevle2Styles.activeColor
											: setMenuItemLevle2Styles.fontColor
											? setMenuItemLevle2Styles.fontColor
											: ''
									};
              font-size: ${
					setMenuItemLevle2Styles.fontSize
						? setMenuItemLevle2Styles.fontSize
						: '22px'
				};
              font-weight:${
					setMenuItemLevle2Styles.fontWeight
						? setMenuItemLevle2Styles.fontWeight
						: '500'
				};
        font-family:${
			setMenuItemLevle2Styles.fontFamily
				? setMenuItemLevle2Styles.fontFamily
				: ''
		}
        `"
									class="column_l_nav_style1"
									><slot name="nav-left-Levle2-icon"></slot>
									<!-- <top>
       在父组件中使用子组件的插槽 
      <template v-slot:nav-top-content-top>
        <p>这是父组件中使用的内容。</p>
      </template>
      </top>-->{{ sitem.title_name }}</span
								>
							</template>
							<el-menu-item
								v-for="(ssitem, j) in sitem.childrenList"
								:key="j"
								:index="ssitem.uid + ''"
								:style="`color: ${
									currentChoose == ssitem.uid
										? setMenuItemLevle3Styles.activeColor
										: setMenuItemLevle3Styles.fontColor
										? setMenuItemLevle3Styles.fontColor
										: ''
								};
              font-size: ${
					setMenuItemLevle3Styles.fontSize
						? setMenuItemLevle3Styles.fontSize
						: '22px'
				};
              font-weight:${
					setMenuItemLevle3Styles.fontWeight
						? setMenuItemLevle3Styles.fontWeight
						: '500'
				};
        font-family:${
			setMenuItemLevle3Styles.fontFamily
				? setMenuItemLevle3Styles.fontFamily
				: ''
		}
        `"
								class="column_l_nav_style3"
								><slot name="nav-left-Levle3-icon"></slot>
								<!-- <top>
       在父组件中使用子组件的插槽 
      <template v-slot:nav-top-content-top>
        <p>这是父组件中使用的内容。</p>
      </template>
      </top>-->{{ ssitem.title_name }}</el-menu-item
							>
						</el-submenu>
					</el-menu-item-group>
				</el-submenu>
				<el-menu-item
					v-if="item.childrenList.length <= 0"
					:index="item.uid"
					class="column_l_nav_item"
				>
					<span
						slot="title"
						class="column_l_nav_style1"
						style="width: 100%; height: 100%; display: block"
						:style="`color: ${
							currentChoose == item.uid
								? setMenuItemLevle1Styles.activeColor
								: setMenuItemLevle1Styles.fontColor
								? setMenuItemLevle1Styles.fontColor
								: ''
						};
              font-size: ${
					setMenuItemLevle1Styles.fontSize
						? setMenuItemLevle1Styles.fontSize
						: '22px'
				};
              font-weight:${
					setMenuItemLevle1Styles.fontWeight
						? setMenuItemLevle1Styles.fontWeight
						: '500'
				};
        font-family:${
			setMenuItemLevle1Styles.fontFamily
				? setMenuItemLevle1Styles.fontFamily
				: ''
		}
        `"
						><slot name="nav-left-Levle1-icon"></slot>
						<!-- <top>
       在父组件中使用子组件的插槽 
      <template v-slot:nav-top-content-top>
        <p>这是父组件中使用的内容。</p>
      </template>
      </top>-->{{ item.title_name }}</span
					>
				</el-menu-item>
				<slot name="nav-left-content-bottom"></slot>
				<!-- <top>
       在父组件中使用子组件的插槽 
      <template v-slot:nav-left-content-bottom>
        <p>这是父组件中使用的内容。</p>
      </template>
      </top>-->
			</div>
		</el-menu>
	</div>
</template>
<script>
export default {
	props: [
		'eventBusId', //点击时消息发送,不能重复
		/**
		 * 消息可能会重复
		 */
		'titleStyles', //标题的其他设置,和hierarchicalStyles参数一致 多一个titleBackground
		/**  iconStyle暂时无用 可以不传
		 *{
				fontColor: 'blue',
				fontSize: '18',
				fontFamily: '',
				iconStyle: {
					type: 'image',
					content: './',
				},
				titleBackground: {
					type: 'image',
					content: './images/titlebg.png',
				},
			}
		 */
		'customContent', //拼装数据:全部数据的数组
		/**
		 *注意:目前只设计了三级,如果三级往后就无法打印出来了,因为这块没完全用迭代的写法,因为要给每一个级别设置不一样的东西
     {
				titlename: titleName,
				levelTitleStyles: levelTitleStyles,
				dataList: [
          {
					//数据
					dataList: {
            uid,
            order_number,
            title_name,
            id,
            childrenList
          },
				}
        ],
			};
		 */
		'setMenuItemLevle1Styles', //一级的样式
		'setMenuItemLevle2Styles', //二级的样式
		'setMenuItemLevle3Styles', //三级的样式
		/**
		 * styles:{activeColor:'',fontColor:'',fontSize:'',fontFamily:'',iconStyle:{}}}
		 * activeColor:点击之后的文字颜色
		 * fontColor:文字颜色
		 * fontSize:文字大小
		 * fontFamily:文字字体
		 * iconStyle:导航当前行的内容前面的icon
		 */
		/** */
		'navWidth', //导航宽度
		'navMinHeight', //导航宽度
		/**传参示例
     * 		<leftCloumn
			class="aaaaa" //自定义类名
			:titleStyles="{ //标题的样式
				fontColor: 'blue',
				fontSize: '18',
				fontFamily: '',
				iconStyle: {
					type: 'image',
					content: './',
				},
				titleBackground: { //标题图片
					type: 'image',
					content: './images/titlebg.png',
				},
			}"
			navWidth="320" //整个组件的宽度
			navMinHeight="300"
			:customContent="assemblyList"
			:setMenuItemLevle1Styles="{
				activeColor: 'yellow',
				fontColor: 'blue',
				fontSize: '18px',
				fontWeight: '500',
				fontFamily: '',
				iconStyle: {
					type: 'text',
					content: 'bbb',
				},
			}"
			:setMenuItemLevle2Styles="{
				activeColor: 'red',
				fontColor: 'blue',
				fontSize: '12px',
				fontWeight: '500',
				fontFamily: '',
				iconStyle: {
					type: 'text',
					content: 'bbb',
				},
			}"
			:setMenuItemLevle3Styles="{
				activeColor: 'green',
				fontColor: 'blue',
				fontSize: '12px',
				fontWeight: '500',
				fontFamily: '',
				iconStyle: {
					type: 'text',
					content: 'bbb',
				},
			}"
		>
			<template v-slot:nav-left-title-icon>
				<span>icon</span>
			</template>
			<template v-slot:nav-left-Levle1-icon>
				<span style="vertical-align: bottom">icon</span>
			</template>
			<template v-slot:nav-left-Levle2-icon>
				<span style="vertical-align: bottom">icon</span>
			</template>
			<template v-slot:nav-left-Levle3-icon>
				<span style="vertical-align: bottom">icon</span>
			</template></leftCloumn
		>
     *
     */
	],
	components: {},
	data() {
		return {
			navData: [], //导航栏数据
			topTitle: '', //左侧导航头部标题
			currentChoose: '', //当前选中的栏目uid
			setDivBackground: {}, //设置导航块的样式
			setTitleBackground: {}, //设置导航title的样式
		};
	},
	computed: {},
	created() {},
	mounted() {
		this.$nextTick(() => {
			//导航样式
			this.styleStingNav();
			//导航标题设置
			this.styleStingNavTitle();
			//导航数据内容
			this.fetchNavList();
		});
	},
	methods: {
		//设置导航样式
		styleStingNav() {
			// console.log(this.$props);
			//判断有没有传入块的样式及内容
			if (this.$props.divBackground) {
				//如果传入
				if (this.$props.divBackground.type == 'image') {
					//如果是图片
					this.setDivBackground = {
						//根据图片地址
						backgroundImage:
							'url(' +
							require(this.$props.divBackground.content + '') + //注意这里require访问静态路径是不能直接使用变量的,需要用``字符串模板或者是后面加个空字符串
							')',
					};
				} else {
					//如果是颜色
					this.setDivBackground = {
						background: this.$props.divBackground.content,
					};
				}
			} else {
				//什么都没传
				this.setDivBackground = { background: 'fff' };
			}

			//设置宽高--------------------------------------------------------
			if (this.$props.navWidth) {
				this.setDivBackground.width = this.$props.navWidth + 'px';
			}
			if (this.$props.navMinHeight) {
				this.setDivBackground.minHeight =
					this.$props.navMinHeight + 'px';
			}
		},
		styleStingNavTitle() {
			let styles = this.$props.titleStyles;
			//判断有没有传入title的样式及内容---------------------------------------
			if (styles.titleBackground) {
				//如果传入
				if (styles.titleBackground.type == 'image') {
					//如果是图片
					this.setTitleBackground = {
						//根据图片地址
						backgroundImage:
							'url(' +
							require(styles.titleBackground.content + '') + //注意这里require访问静态路径是不能直接使用变量的,需要用``字符串模板或者是后面加个空字符串
							')',
					};
				} else {
					//如果是颜色
					this.setTitleBackground = {
						background: styles.titleBackground.content,
					};
				}
			} else {
				this.setTitleBackground = { background: 'fff' };
			}
			this.setStyles(0, styles);
		},
		//获取导航数据
		fetchNavList() {
			let _this = this;
			//设置标题名称
			if (this.$props.customContent.titlename) {
				this.topTitle = this.$props.customContent.titlename;
			}
			//先遍历获取的数据,然后根据传参选择最终样式
			if (this.$props.customContent) {
				let list = this.$props.customContent.dataList;
				//获取数据打印-设置数据
				for (let index = 0; index < list.length; index++) {
					const element = list[index];
					_this.navData.push(element.dataList);
				}
			}
		},
		//根据不同级别设置样式

		setStyles(level, styles) {
			if (styles.fontColor) {
				this.setTitleBackground.color = styles.fontColor;
			}
			if (styles.fontSize) {
				this.setTitleBackground.fontSize = styles.fontSize;
			}
			if (styles.fontFamily) {
				this.setTitleBackground.fontFamily = styles.fontFamily;
			}
		},
		//点击带有子节点的menu选项
		selectMenu2(uid) {
			let _this = this;
			//点击之后用eventbus传递数据
			_this.$eventBus.$emit(`${this.$props.eventBusId}`, [uid]);
			for (let i = 0; i < _this.navData.length; i++) {
				if (_this.navData[i].uid == uid) {
					_this.currentChoose = uid;
					return; // 终止循环
				} else {
					if (_this.navData[i].childrenList.length > 0) {
						for (
							let j = 0;
							j < _this.navData[i].childrenList.length;
							j++
						) {
							if (_this.navData[i].childrenList[j].uid == uid) {
								_this.currentChoose = uid;
								return; // 终止循环
							} else {
								if (
									_this.navData[i].childrenList[j]
										.childrenList.length > 0
								) {
									for (
										let k = 0;
										k <
										_this.navData[i].childrenList[j]
											.childrenList.length;
										k++
									) {
										if (
											_this.navData[i].childrenList[j]
												.childrenList[k].uid == uid
										) {
											_this.currentChoose = uid;
											return; // 终止循环
										}
									}
								}
							}
						}
					}
				}
			}
		},
		//点击选择menu栏目
		selectMenu(uid) {
			// console.log(uid);
			let _this = this;
			_this.$eventBus.$emit(`${this.$props.eventBusId}`, [uid]);
			for (let i = 0; i < _this.navData.length; i++) {
				if (_this.navData[i].uid == uid) {
					_this.currentChoose = uid;
				} else {
					if (_this.navData[i].childrenList.length > 0) {
						for (
							let j = 0;
							j < _this.navData[i].childrenList.length;
							j++
						) {
							if (_this.navData[i].childrenList[j].uid == uid) {
								_this.currentChoose = uid;
							} else {
								if (
									_this.navData[i].childrenList[j]
										.childrenList.length > 0
								) {
									for (
										let k = 0;
										k <
										_this.navData[i].childrenList[j]
											.childrenList.length;
										k++
									) {
										if (
											_this.navData[i].childrenList[j]
												.childrenList[k].uid == uid
										) {
											_this.currentChoose = uid;
										}
									}
								}
							}
						}
					}
				}
			}
		},
	},
};
</script>
<style scoped lang="less">
// 左侧导航部分
.column_l_nav_box {
	// float: left;/
	width: 300px;
	// min-height: 460px;
	padding: 20px;
	background: transparent;
	// border: 1px solid #e9e9e9;
	// box-shadow: 0px 0px 6px 0px #eeeeee;
	position: relative;
	// background-size: 100% 100%;
	background-size: cover;
	background-repeat: no-repeat;
	background-position: center;
	// padding-bottom: 232px;
}
.column_l_nav_title {
	// width: 260px;
	height: 50px;
	text-align: center;
	line-height: 50px;
	font-size: 18px;
	font-family: Source Han Sans CN;
	font-weight: 500;
	color: #f7f7f7;
	background-size: cover;
	background-repeat: no-repeat;
	background-position: center;
	margin-bottom: 20px;
}
.column_l_nav_title2 {
	width: 260px;
	height: 50px;
	line-height: 50px;
	font-family: Source Han Sans CN;
	font-weight: bold;
	color: #439238;
	margin-bottom: 10px;
	margin-top: 10px;
	border-bottom: 1px solid #439238;
	font-size: 16px;
	text-align: center;
}
.column_l_nav_item {
	background: #f2f2f2;
	margin-bottom: 8px;
	font-size: 15px;
	font-family: Source Han Sans CN;
	font-weight: bold;
	color: #3f433f;
}
.column_l_nav_img {
	position: absolute;
	bottom: 0;
	left: 0;
	width: 100%;
}
.column_l_nav_style1 {
	font-size: 15px;
	font-family: Source Han Sans CN;
	font-weight: bold;
	// color: #3F433F;
}
.column_l_nav_style2 {
	border-bottom: 1px solid #f3f3f3;
	font-size: 18px;
	font-family: Source Han Sans CN;
	font-weight: 400;
	color: #8c8c8c;
}
.column_l_nav_style3 {
	font-size: 13px;
	font-family: Source Han Sans CN;
	font-weight: 400;
	color: #808080;
}
.column_l_nav_style3:hover {
	color: #4eac41;
}

// 右侧显示部分
.column_r_box {
	float: right;
	width: 868px;
	// padding:14px 30px;
	min-height: 578px;
	// background: #FFFFFF;
	border: 1px solid #e9e9e9;
	box-shadow: 0px 0px 6px 0px #eeeeee;
}
.el-menu {
	background-color: transparent;
}
</style>

移动端:
简陋版

	<div v-if="isMobile">
			<!-- <Tabs v-for="index in 8" :title="'标签' + index" :key="index">
				<Tab>{{ index }}</Tab>
			</Tabs> -->
			<h5 class="column_l_nav_title" :style="setTitleBackground">
				<slot name="nav-left-title-icon"></slot>
				<!-- <top>
       在父组件中使用子组件的插槽 
      <template v-slot:nav-top-content-top>
        <p>这是父组件中使用的内容。</p>
      </template>
      </top>-->
				{{ topTitle }}
			</h5>
			<Tabs type="card">
				<Tab
					v-for="(item, index) in navData"
					:title="item.title_name"
					:key="index"
				></Tab>
			</Tabs>
		</div>

2.标签行

展开隐藏,根据宽度算出来

  1. 必须要先把原本加载的所有项先铺设在页面上,使用abslout隐藏原本所有的项
  2. 然后根据页面预留的宽度计算每一项的宽度,比如tag1的宽度+tag2+tag3+tag4等等,如果最后一个内容的宽度加起来超过了最大预设的宽度那么最后一个内容就在展开的列表中显示,其他的在第一行显示
  3. 还有选择显示选择框,反选,去掉选中的效果
    在这里插入图片描述

在这里插入图片描述
组件代码

<template>
	<div class="container">
		<div class="tags-container">
			<div
				v-for="(item, index) in tags"
				:key="index + '-only'"
				class="tagNone"
				:class="tagTitle"
			>
				{{ item.name }}
			</div>
			<div class="radioTitle">{{ tagTitle }}</div>
			<div
				v-for="(item, index) in visibleTags"
				:key="index"
				class="tag"
				:class="{ active: isActive(item) }"
				@click="tagClick(item)"
			>
				{{ item.name }}
			</div>
			<div
				v-if="showToggle"
				class="toggle-button tagMore"
				@click="toggleShowMore"
			>
				<span style="margin-right: 5px">更多...</span>
				<span
					class="showMoreBorder"
					:style="{ lineHeight: showMore ? '12px' : '16px' }"
					>{{ showMore ? '-' : '+' }}</span
				>
			</div>
		</div>
		<div class="tags-container" v-if="showMore">
			<div
				v-for="(item, index) in hiddenTags"
				:key="'more-' + index"
				class="tag"
				:class="{ active: isActive(item) }"
				@click="tagClick(item)"
			>
				{{ item.name }}
			</div>
		</div>
	</div>
</template>

<script>
export default {
	name: 'TagsComponent',
	props: {
		tags: {
			type: Array,
			required: true,
		},
		selectedTags: {
			type: Array,
			default: () => [],
		},
		tagTitle: {
			type: String,
			required: true,
		},
	},
	data() {
		return {
			containerWidth: 740,
			showMore: false,
			visibleTags: [],
		};
	},
	computed: {
		hiddenTags() {
			return this.tags.slice(this.visibleTags.length);
		},
		showToggle() {
			return this.hiddenTags.length > 0;
		},
	},
	mounted() {
		this.visibleTagsMounth();
	},
	methods: {
		tagClick(tag) {
			this.$emit('tag-click', tag);
		},
		isActive(tag) {
			for (let index = 0; index < this.selectedTags.length; index++) {
				const element = this.selectedTags[index];
				if (element.type == tag.type && tag.name == element.name) {
					return true;
				}
			}
		},
		toggleShowMore() {
			this.showMore = !this.showMore;
		},
		visibleTagsMounth() {
			let totalWidth = 0;
			for (let i = 0; i < this.tags.length; i++) {
				const tag = this.tags[i];
				const tagWidth =
					document.getElementsByClassName(this.tagTitle)[i]
						.clientWidth + 0;

				if (totalWidth + tagWidth <= this.containerWidth) {
					this.visibleTags.push(tag);
					totalWidth += tagWidth;
					console.log(totalWidth);
				} else {
					break; // 超过容器宽度时停止添加标签
				}
			}
		},
	},
};
</script>

<style scoped lang="less">
.container {
	width: 850px;
	overflow: hidden;
}

.tags-container {
	display: flex;
	flex-wrap: wrap;
	position: relative;
	font-size: 14px !important;
	.radioTitle {
		font-weight: 600;
		padding: 12px 0;
	}
}
.tagNone {
	height: 0;
	opacity: 0;
	position: absolute;
	z-index: -1;
	padding: 12px 10px;
	font-size: 14px !important;
}
.tag {
	padding: 12px 10px;
	color: rgba(34, 34, 34, 1);
	cursor: pointer;
	user-select: none;
	transition: color 0.3s;
}
.tagMore {
	padding: 12px 0 !important;
}
.tag:hover {
	color: rgba(54, 163, 82, 1);
}

.tag.active {
	color: rgba(54, 163, 82, 1);
}

.toggle-button {
	margin-left: auto;
	padding: 10px;
	color: #6c757d;
	cursor: pointer;
	user-select: none;
	transition: color 0.3s;
	display: flex;
	.showMoreBorder {
		width: 15px;
		height: 15px;
		display: inline-block;
		line-height: 16px;
		text-align: center;
		font-size: 20px;
		border: 1px solid #b2b2b2;
	}
}

.toggle-button:hover {
	color: #007bff;
}

.selected-tags {
	margin-top: 10px;
	display: flex;
	flex-wrap: wrap;
}

.el-tag {
	margin-right: 5px;
	margin-bottom: 5px;
}
</style>


引用

		<downPull
				class="downPull"
				v-for="(item, index) in tagsLists"
				:key="index"
				:tags="item.tags"
				:tagTitle="item.tagTitle"
				:selected-tags="selectedTags"
				@tag-click="handleTagClick"
			></downPull>
import downPull from './downPull.vue';
components: { downPull },
data():{
	return{
		tagsLists: [
				{
					tagTitle: '国家',
					tags: [
						{ name: '不限', type: 'country' },
						{ name: '中国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
						{ name: '美国', type: 'country' },
					],
				},
				{
					tagTitle: '省份',
					tags: [
						{ name: '不限', type: 'province' },
						{ name: '省份1', type: 'province' },
						{ name: '省份2', type: 'province' },
						{ name: '省份3', type: 'province' },
					],
				},

				{
					tagTitle: '行业',
					tags: [
						{ name: '不限', type: 'industry' },
						{ name: '行业1', type: 'industry' },
						{ name: '行业2', type: 'industry' },
						{ name: '行业3', type: 'industry' },
					],
				},
				{
					tagTitle: '年份',
					tags: [
						{ name: '不限', type: 'year' },
						{ name: '2021', type: 'year' },
						{ name: '2022', type: 'year' },
						{ name: '2023', type: 'year' },
					],
				},
			],
			selectedTags: [
				{ name: '不限', type: 'country' },
				{ name: '不限', type: 'province' },
				{ name: '不限', type: 'year' },
				{ name: '不限', type: 'industry' },
			], // 初始化选中"不限"
}
},
methods: {
	// 处理标签点击事件
		handleTagClick(tag) {
			// 根据点击的标签找到对应的标签组
			const tagList = this.tagsLists.find((list) =>
				list.tags.some(
					(t) => t.name === tag.name && t.type === tag.type
				)
			);

			if (tagList) {
				if (tag.name === '不限') {
					// 如果点击的是 "不限" 标签,则选中对应类型的 "不限" 标签
					this.selectedTags = this.selectedTags.filter(
						(t) => t.type !== tag.type
					); // 先移除同类型的其他 "不限" 标签
					this.selectedTags.push({ name: '不限', type: tag.type }); // 添加当前选中的 "不限" 标签
				} else {
					// 如果点击的是其他标签,则根据当前选中状态进行添加或移除操作
					const index = this.selectedTags.findIndex(
						(t) => t.name === tag.name && t.type === tag.type
					);
					if (index !== -1) {
						this.selectedTags.splice(index, 1); // 移除已选中的标签
					} else {
						// 添加当前选中的标签
						this.selectedTags.push(tag);
					}

					// 移除当前标签组的 "不限" 标签
					const noLimitTag = tagList.tags.find(
						(t) => t.name === '不限'
					);
					if (noLimitTag) {
						const noLimitIndex = this.selectedTags.findIndex(
							(t) => t.name === '不限' && t.type === tag.type
						);
						if (noLimitIndex !== -1) {
							this.selectedTags.splice(noLimitIndex, 1);
						}
					}
				}

				// 检查当前标签组是否还有其他选中标签,如果没有则重新选中对应类型的 "不限" 标签
				const otherTagsSelected = tagList.tags.some(
					(t) =>
						t.name !== '不限' &&
						this.selectedTags.some(
							(selected) =>
								selected.name === t.name &&
								selected.type === t.type
						)
				);
				if (!otherTagsSelected) {
					const noLimitTag = tagList.tags.find(
						(t) => t.name === '不限'
					);
					if (noLimitTag) {
						const noLimitIndex = this.selectedTags.findIndex(
							(t) => t.name === '不限' && t.type === tag.type
						);
						if (noLimitIndex === -1) {
							this.selectedTags.push({
								name: '不限',
								type: tag.type,
							});
						}
					}
				}
			}
		},

		// 移除标签
		removeTag(tag) {
			// 根据移除的标签找到对应的标签组
			const tagList = this.tagsLists.find((list) =>
				list.tags.some(
					(t) => t.name === tag.name && t.type === tag.type
				)
			);

			if (tagList) {
				if (tag.name === '不限') {
					return; // 不执行移除操作
				}
				const index = this.selectedTags.findIndex(
					(t) => t.name === tag.name && t.type === tag.type
				);
				if (index !== -1) {
					this.selectedTags.splice(index, 1); // 移除选中的标签
				}

				// 如果当前标签组没有其他选中标签,则重新选中对应类型的 "不限" 标签
				const noLimitTag = tagList.tags.find((t) => t.name === '不限');
				if (
					noLimitTag &&
					this.selectedTags.filter((t) => t.type === tag.type)
						.length === 0
				) {
					this.selectedTags.push({
						name: '不限',
						type: noLimitTag.type,
					});
				}
			}
		},
}
  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值