UniApp插槽

前言

emmmmm遇到的插槽需求,记录一下,后续如果需要就可以看看

使用的时uniapp+springboot

需求是做一个问题发布和评论,要求每个插槽显示不同类型,并且数据显示要求能看到第一条评论的信息,类似某乎

前端页面效果

前端页面(不含样式)

<template>
    <view class="content">
	    <TabBar :tabs="tabList" listBgColor="#fff" @getData="getListData">		
	        <template v-for="i in tabList" v-slot:[i.name]>
	            <TopicItem class="topic" @jump="openInfoPage" v-for="(item, index) in list" :data="item" :key="index"></TopicItem>
            </template>
	    </TabBar>
    </view>
</template>

1.uniapp使用view作为容器,可以看作是html中的div

2.通过TabBar组件来渲染插槽,将tabList 对象传递给 TabBar,TabBar 组件中会触发getData方法会调用 getListData 方法,从而获取数据渲染到插槽中

3.通过TopicItem 抽象容器 来遍历 tabList,v-slot:[i.name]TabBar 组件中的插槽指定一个动态名称,用来实现了标签页切换时对应内容的动态渲染

4.TopicItem 组件放置在插槽中,用于显示各个插槽中的内容,通过@jump设置事件监听器件并调用 openInfoPage 方法,遍历 list 数组元素赋值给 data 属性传递给 TopicItem 组件

<script>
import { list } from '@/api/bbs.js';
import TabBar from './components/tab-bar.vue';
import TopicItem from './components/topic-item.vue';

export default {
	components: {
		TabBar,
		TopicItem
	},
	data() {
		return {
            tabList: [
			{ name: 'choice', title: '精选', type: 0 }, 
			{ name: 'newest', title: '最新', type: 1 }, 
			{ name: 'follow', title: '关注', type: 2 }],
        }
    },
    onLoad() {
		this.getListData({ type: 0 });
	},
    methods: {
        async getListData(data) {
			const resp = await list({
				type: data.type,
				pageNum: 1,
				pageSize: 10
			});
			if (resp.code != 200) {
				this.list = null;
			}else {
				this.list = resp.data;
			}
		},
    }  
}
</script>

1.通过import 引入TabBar和TopicItem两个组件,并初始化组件

2.onload函数用于在页面加载完成后执行的操作,加载type为0插槽的数据

3.async异步函数结合await函数 执行 list方法,携带类型和页数参数,再判断状态码返回数据

前端组件

1.TabBar组件

(实际上是插槽的页面效果,实现了选项卡切换功能)

①类名为 "list" 的 <view> 元素,用于显示选项卡的标题部分,并判断是否选中标题来切换样式

②类名为 "tab-content" 的 <view> 元素,用于显示选项卡对应的内容部分

通过 <slot> 插槽语法,使用:name属性绑定了当前选项卡的名称,即在父页面中定义的v-slot:[i.name]  ,这样做的作用是将父组件传递给子组件的内容插入到这个位置,实现了动态插入不同选项卡对应的内容

【v-slot 用于命名插槽的指令,而 <slot> 元素用于定义插槽内容的占位符】

props 属性是用来接收父组件传递过来的数据

switchTab 方法用于切换选项卡并更新当前选中的选项卡的名称用于修改样式,且接收参数 item(选项卡对象并通过 $emit 方法向父组件发送一个自定义事件 getData来获取数据

<template>
<view class="tab-bar">
        <!-- 标题部分 -->
		<view class="list" :class="listBottomBorder ? 'is-border-bottom' : ''"
                :style="listStyle">
			<text class="tab" :class="tabIndex == i.name ? 'tab-selected' : ''" 
                v-for="(i, k) in tabs" :key="k" @click="switchTab(i)">
				{{ i.title }}
			</text>
		</view>
        <!-- 内容部分 -->
		<view class="tab-content">
			<view class="tab-item-box" v-for="(i, k) in tabs" :key="k" 
                v-if="tabIndex == i.name">
			<slot :name="i.name"></slot>
			</view>
		</view>
	</view>
</template>

<script>
export default {
	props: {
		tabs: Array,
		listBgColor: String,
		listBottomBorder: Boolean,
		listBottomBorderColor: String
	},
	data() {
		return {
			tabIndex: this.tabs[0].name,
			listStyle: {
				'--backgroundColor': this.listBgColor,
				'--listBottomBorderColor': this.listBottomBorderColor
			}
		};
	},
	methods: {
		switchTab(item) {
			this.tabIndex = item.name;
			this.$emit('getData', item);
		}
	}
};
</script>

<style lang="less" vars="{ color  }" scoped>
.tab-bar {
	.is-border-bottom {
		border-bottom: 1px solid var(--listBottomBorderColor);
	}
	.list {
		display: flex;
		justify-content: flex-start;
		align-items: center;
		padding-left: 1em;
		font-size: 1rem;
		background-color: var(--backgroundColor);
		.tab {
			position: relative;
			padding: 0.5em 0;
			margin-right: 2em;
			color: #a1a4ab;

			&::after {
				display: none;
				content: '';
				position: absolute;
				left: 50%;
				bottom: 0;
				transform: translateX(-50%);
				width: 1.5em;
				height: 3px;
				background-color: #57deb0;
			}
		}
		.tab-selected {
			color: #57deb0;
			&::after {
				display: inline-block;
			}
		}
	}

	.tab-content {
		.tab-item-box {
			background-color: #f7f8f9;
			.topic-item {
				background-color: #fff;
			}
		}
	}
}
</style>
2.TopicItem组件(不含样式)

(TopicItem组件相对业务化,通过获取后台数据展示在页面)

①通过props 属性是用来接收父组件传递过来的数据并判断数据是否为空,为空渲染空数据icon

②获取返回数据中的标题/用户头像/用户名/用户评论及其对应的统计数

<template>
	<view class="topic-item" @click="jump">
		
		<view v-if="data.length == 0" class="pa-center">
			<u-empty mode="data" icon="http://cdn.uviewui.com/uview/empty/data.png"></u-empty>
		</view>
		
	
		<view class="header">
			<!-- 获取话题名 -->
			<text class="title two-line-ellipsis">{{ data.title }}</text>
		</view>
		<!-- 存在回复 -->
		<view class="main" v-if="data.reply">
			<!-- 回复人的头像昵称 -->
			<view class="user">
				<image class="avatar" :src="data.reply.userImg" mode="aspectFit"></image>
				<text class="name">{{ data.reply.userName }}</text>
			</view>
			<!-- 回复内容的文本 -->
			<view class="content">
				<text class="info two-line-ellipsis">{{ data.reply.replyContent }}</text>
			</view>
			<!-- 回复内容的图片 -->
			<view class="img-box">
				<view class="box">
					<image class="img"
						:class="count == 3 ? 'one-column' : count == 6 ? 'two-column' : count == 9 ? 'three-column' : ''"
						v-for="(i, k) in data.reply.imgList" :key="k"
						:src="i" mode="aspectFill"
					></image>
				</view>
			</view>
			<!-- 获取话题的统计数 -->
			<view class="status">
				<view class="item">
					<u-icon name="heart" color="#a1a4ab" size="1.2rem"></u-icon>
					<text class="type">{{ data.reply.likeCount }}</text>
				</view>
				<view class="item">
					<u-icon name="chat" color="#a1a4ab" size="1.2rem"></u-icon>
					<text class="type">{{ data.reply.commentCount }}</text>
				</view>
				<view class="item">
					<u-icon name="star" color="#a1a4ab" size="1.2rem"></u-icon>
					<text class="type">{{ data.reply.collectCount }}</text>
				</view>
			</view>
		</view>
		<view class="not-reply" v-else>
			<view class="box"><text>暂无回答</text></view>
		</view>
	</view>
</template>

<script>
export default {
	props: {
		tab: Object,
		data: Object
	},
	data() {
		return {
			count: 6
		};
	},
	methods: {
		jump() {
			let params = {};
			if (this.data.reply) {
				params.isReply = true;
				params.id = this.data.reply.id;
			} else {
				params.isReply = false;
				params.id = this.data.id;
			}
			this.$emit('jump', params);
		}
	}
};
</script>

后端

1.前端接口

export const list = (params) => http.get('/api/bbs/list', {
	params
})

2.后端控制层


@Slf4j
@RestController
@RequestMapping("/bbs")
public class TopicController {

    @Autowired
    private TopicService topicService;

    @GetMapping("/list")
    public R<List<TopicItemVo>> list(@LoginUser Integer userId,
                                     @RequestParam(value = "type", defaultValue = "0") Integer type,
                                     @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
                                     @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
        List<TopicItemVo> topicItemVoList = topicService.list(userId, type, pageNum, pageSize);
        return RespUtils.success(topicItemVoList);
    }
}

3.业务实现

@Service
public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements TopicService {
    @Override
    public List<TopicItemVo> list(Integer userId,Integer type,Integer pageNum,Integer pageSize) {
        
        PageHelper.startPage(pageNum, pageSize);// 分页
        LambdaQueryWrapper<Topic> wrapper = Wrappers.lambdaQuery(Topic.class);
        wrapper.eq(Topic::getStatus, 1);//状态正常
        //精选
        if (type.equals(0)) {
            wrapper.gt(Topic::getReplyCount, 20);//回复数>20
            wrapper.orderByDesc(Topic::getPvCount);//排序浏览量
            List<Topic> topicList = this.list(wrapper); // 执行查询
            // 遍历查询结果,转换为视图对象
            topicList.forEach(topic -> {
                TopicItemVo vo = new TopicItemVo();
                vo.setId(topic.getId());
                vo.setTitle(topic.getTitle());
                vo.setUserId(topic.getUserId());
            });
        //最新
        } else if (type.equals(1)) {
            wrapper.orderByDesc(Topic::getCreateTime);//根据最新时间排序
        //关注
        } else if (type.equals(2)) {
            List<TopicFollow> followList = new ArrayList<TopicFollow>();
            followList = topicFollowService.getFollowList(userId);
            if (followList != null && !followList.isEmpty()) {
                List<Long> topicIds = followList.stream()
                        .map(topic -> Long.valueOf(topic.getTopicId()))
                        .collect(Collectors.toList());
                wrapper.in(Topic::getId, topicIds);
            } else {
                throw new BusinessException("请关注你的问题哦");
            }
        }

        List<Topic> topicList = this.list(wrapper); // 执行查询
        List<TopicItemVo> voList = new ArrayList<>();
        // 遍历查询结果,转换为视图对象
        topicList.forEach(topic -> {
            TopicItemVo vo = new TopicItemVo();
            vo.setId(topic.getId());
            vo.setTitle(topic.getTitle());
            vo.setUserId(topic.getUserId());

            //获取主题的首条回复,并进行HTML标签的解析
            TopicReplyDto replyDto = topicReplyService.firstReply(topic.getId());
            if (Objects.nonNull(replyDto)) {
                TopicReplyVo reply = BeanUtil.copyProperties(replyDto, TopicReplyVo.class);
                reply.setReplyContent(Jsoup.parse(reply.getReplyContent()).text());
                vo.setReply(reply);
            }
            voList.add(vo);
        });
        return voList;
    }
}

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值