个人知识碎片的体系化理解与整理,缓慢更新,理解不到位与错漏之处望见谅与指正。
day06摘要思想:vue中v-for指令可对数组进行遍历并根据每个数组元素单独进行组件渲染,配合基础后台增删改查服务可实现动态评论功能;页面需要使用iView的Grid栅格布局及Card组件等
开发环境:WebStorm 2019.1
1.评论表设计与后台代码生成
表设计如下:
CREATE TABLE `zd_course_comment` (
`id` BIGINT(20) NOT NULL,
`course_id` BIGINT(20) NOT NULL COMMENT '课程id',
`comment_context` VARCHAR(200) NULL DEFAULT '' COMMENT '评论内容',
`comment_name` VARCHAR(50) NULL DEFAULT '' COMMENT '评论者',
`comment_icon` VARCHAR(200) NULL DEFAULT '' COMMENT '评论者头像',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
)
COMMENT='课程评论表'
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB
;
后台Controller层对外接口代码生成:
/**
* 根据Id 查询
*
* @param id
* @return
*/
@GetMapping(value = "/getByPrimaryKey")
public Result getByPrimaryKey(@RequestParam Long id) {
log.info("根据Id查询 CourseComment,参数= {} ", JSONObject.toJSONString(id));
return Result.ok(this.courseCommentService.getByPrimaryKey(id));
}
/**
* 分页查询
*
* @param queryMap
* @return
*/
@GetMapping(value = "/findPage")
public Result findPage(@RequestParam Map<String, Object> queryMap) {
log.info("查询 courseComment 分页列表,参数= {} ", JSONObject.toJSONString(queryMap));
PageUtils record = this.courseCommentService.findPage(queryMap);
return Result.ok(record);
}
/**
* 列表查询
*
* @param queryMap
* @return
*/
@GetMapping(value = "/list")
public Result list(@RequestParam Map<String, Object> queryMap) {
log.info("查询 courseComment 列表,参数= {} ", JSONObject.toJSONString(queryMap));
List<CourseComment> record = this.courseCommentService.list(queryMap);
return Result.ok(record);
}
/**
* 新增数据
*
* @param entity
* @return 新增结果
*/
@PostMapping(value = "/save")
public Result save(@RequestBody @Validated CourseComment entity, BindingResult bindingResult) {
log.info("新增 courseComment,参数= {} ", JSONObject.toJSONString(entity));
if (bindingResult.hasErrors()) {
log.error("新增 courseComment 参数不正确,异常={}", bindingResult.getFieldError().getDefaultMessage());
return Result.error(ResultEnum.ERROR_400.getCode(), bindingResult.getFieldError().getDefaultMessage());
}
this.courseCommentService.save(entity);
return Result.ok();
}
/**
* 更新
*
* @param entity
* @return
*/
@PostMapping(value = "/update")
public Result updateById(@RequestBody @Validated CourseComment entity, BindingResult bindingResult) {
log.info("更新 CourseComment,参数= {} ", JSONObject.toJSONString(entity));
if (bindingResult.hasErrors()) {
log.error("更新 courseComment 参数不正确,异常={}", bindingResult.getFieldError().getDefaultMessage());
return Result.error(ResultEnum.ERROR_400.getCode(), bindingResult.getFieldError().getDefaultMessage());
}
this.courseCommentService.updateById(entity);
return Result.ok();
}
/**
* 删除
*
* @param ids
* @return
*/
@PostMapping(value = "/delete")
public Result deleteById(@RequestBody List<Long> ids) {
log.info("删除 CourseComment,参数= {} ", JSONObject.toJSONString(ids));
this.courseCommentService.deleteById(ids);
return Result.ok();
}
2.前端组件层布局搭建
核心代码如下,其中Row/Col、Card、Menu、Input、Page等均为iView的现成组件,建议参考官方文档使用:
<Row>
<Col span="18">
<video-player id="videoPlayer" class="video-player vjs-custom-skin" ref="videoPlayer" :playsinline="true" :options="playerOptions"></video-player>
<Card id="otherInfoCard" style="height:600px">
<h1 class="cardTitle">{{classData.courseName}}</h1>
<Divider />
</Card>
</Col>
<Col span="6">
<Card style="overflow:auto" id="infoCard">
<div class="videoMenu">
<Menu @on-select="handleClick" mode="horizontal" active-name="1" >
<MenuItem name="1" >课程详情</MenuItem>
<MenuItem name="2">评论({{commentData.total}})</MenuItem>
</Menu>
</div>
<div v-if="menuName === '1'">
<!--课程详情-->
<p style="line-height:40px;margin-top:15px">课程名称:{{classData.courseName}}</p>
<p style="line-height:40px">学段:
<span v-if="classData.stageCode === 1">小学</span>
<span v-if="classData.stageCode === 2">初中</span>
<span v-if="classData.stageCode === 3">高中</span>
</p>
<p style="line-height:40px">年级:{{classData.gradeName}}</p>
<p style="line-height:40px">学科:{{classData.subjectName}}</p>
<p style="line-height:40px">发布时间:{{classData.createTime}}</p>
</div>
<Card shadow v-if="menuName === '2'" class="commentDiv" id="commentCard">
<div v-for="item in commentData.data" :key="item.id">
<!--评论-->
<Row>
<Col span="5">
<img :src="item.commentIcon" style="width:50px;height:50px;margin-top:10%"/>
</Col>
<Col span="19">
<Row>
<Col span="8">
<h4 style="line-height:30px">{{item.commentName}}</h4>
</Col>
<Col span="16" align="right">
<p style="line-height:30px;font-size:12px">{{item.createTime}}</p>
</Col>
</Row>
<p style="line-height:30px;font-size:14px;word-break:break-all;">
{{item.commentContext}}
</p>
</Col>
</Row>
<Divider v-if="(commentData.data.indexOf(item) !== commentData.data.length - 1) || commentData.totalPage > 1"/>
</div>
<Page align="right" v-if="commentData.totalPage > 1" size="small" :total="commentData.total" @on-change="pageChange" :current.sync="commentPageNum" :page-size="commentPageSize" @on-page-size-change="pageSizeChange"></Page>
</Card>
<div v-if="menuName === '2'" style="position:absolute;bottom:2%;width:90%">
<Input v-model="commentModel" :maxlength="Number(200)" placeholder="请输入评论,不超过200字...">
<Button slot="append" @click="commentSubmit">提交</Button>
</Input>
</div>
</Card>
</Col>
</Row>
3.原生js方式进行高度自适应
评论区卡片组件的高度根据左侧组件的高度进行动态修改,需要监听onresize事件并使用document.getElementById获取元素并动态修改其height属性:
mounted() {
//第一次进入时未触发onresize事件,手动调用一次
let infoCard = document.getElementById("infoCard");
let videoPlayer = document.getElementById("videoPlayer");
let otherInfoCard = document.getElementById("otherInfoCard");
infoCard.style.height = String(videoPlayer.offsetHeight + otherInfoCard.offsetHeight) + "px";
//监听onresize事件
window.onresize = () => {
return (() => {
let infoCard = document.getElementById("infoCard");
let videoPlayer = document.getElementById("videoPlayer");
let otherInfoCard = document.getElementById("otherInfoCard");
infoCard.style.height = String(videoPlayer.offsetHeight + otherInfoCard.offsetHeight) + "px";
let commentCard = document.getElementById("commentCard");
if(commentCard !== null) {
commentCard.style.height = String((videoPlayer.offsetHeight + otherInfoCard.offsetHeight) * 0.9) + "px";
}
})();
};
},
对于评论区的内容区域,其在首次进入时因v-if不成立而未加载,若直接使用document.getElementById则将获取到undefined,需要在nextTick中获取:
handleClick(name) {
if(name === "2") {
this.$nextTick(() => {
let commentCard = document.getElementById("commentCard");
if(commentCard !== null) {
let videoPlayer = document.getElementById("videoPlayer");
let otherInfoCard = document.getElementById("otherInfoCard");
commentCard.style.height = String((videoPlayer.offsetHeight + otherInfoCard.offsetHeight) * 0.9) + "px";
}
})
}
this.menuName = String(name)
},
4.实际调用后台接口
首次进入时初始化,分页页数与页码改变触发事件,评论提交触发事件时均需调用分页查询接口,提交时需调用保存接口:
async getCommentData() {
let res = await get('/v1/courseComment/findPage',{
courseId: this.$route.query.id,
pageNum: this.commentPageNum,
pageSize: this.commentPageSize
});
this.commentData = res.data
},
pageChange(p) {
this.commentPageNum = p;
document.documentElement.scrollTop = 0;
document.getElementById("commentCard").scrollTop = 0;
this.getCommentData();
},
pageSizeChange(p) {
this.commentPageSize = p;
document.documentElement.scrollTop = 0;
document.getElementById("commentCard").scrollTop = 0;
this.getCommentData();
},
async commentSubmit() {
if(this.commentModel === '') {
this.$Message.info("评论不能为空!");
}
else {
let res = await post('/v1/courseComment/save',{
courseId: this.$route.query.id,
commentName: this.$store.state.user.userName,
commentContext: this.commentModel,
commentIcon: this.$store.state.user.avatarImgPath
});
if(res.code === 200000) {
this.$Message.success("评论发送成功!");
this.commentPageNum = 1;
document.documentElement.scrollTop = 0;
document.getElementById("commentCard").scrollTop = 0;
this.commentModel = '';
this.getCommentData();
}
else {
this.$Message.error(res.msg);
}
}
}
5.实现效果
页面效果如下: