1. 业务开发

下面是核心的前端调用代码,具体页面效果根据自身模版的风格进行调整。

[!tip] dataType是字典的评论类型,前台在对接业务时需注意,请传递字典label值。 注意:基于业务需求以及安全性考虑,评论插件web端和会员层接口只能保存或查看类型为content的文章评论,如果涉及新的业务场景,建议新增web端接口处理。

1.1. 前端开发

后台需要在自定义字典中增加字典值;找到评论类型,再添加新的字典,如(名称label:文章,数据值value:content)数据值作为数据保存,名称作为展示;

1.1.1. 游客查看文章(content)类型评论

代码片段参考

下面提供的是展示方法是将获取所有评论数据,然后根据top_id(顶级评论id)comment_id(父评论id)和自身id的关系来处理形成评论层级关系

//vue代码片段
...
<div v-for="comment in commentDataList">
  {{comment.peopleName?comment.peopleName:'游客'}}
  {{comment.commentTime}}
  {{comment.commentContent}}
    <div v-for="childComment in comment.childCommentDataLists">
        {{comment.peopleName?comment.peopleName:'游客'}}
        {{comment.commentTime}}
        {{comment.commentContent}}
    </div>
</div>
...

...
ms.http.get('/comment/list.do', that.form).then(function (res) {
    if (res.result) {
        that.total = res.data.total; //总数
        that.commentList = res.data.rows; //评论记录   
        // 第一级评论
        var topComment = that.commentList.filter(c => !c.topId || c.topId==0)
        topComment.forEach(function (data) {
            // 如果当前这条评论存在图片 
            ...
            if (data.commentPicture){
                var commentPicture = JSON.parse(data.commentPicture)
                // 存储当前评论的图片
                data['pictureList'] = []
                commentPicture.forEach(function (picture){
                    data['pictureList'].push(picture.path)
                })
            }
            ...
            //初始化子评论列表
            data.childCommentDataLists = [];
            // 当前遍历到评论的所有子评论
            var childComments = allComment.filter(item => item.topId && item.topId==data.id)
            childComments.forEach(function (childData) {
                // 如果父评论为顶级评论则直接赋值
                if (childData.commentId == data.id){
                    childData.parentName = data.peopleName;
                } else {
                    // 找到当前遍历子评论的父评论
                    var parentComment = childComments.find(function (item) {
                        return item.id = childData.commentId;
                    })
                    if (parentComment){
                        childData.parentName = parentComment.peopleName;
                    }
                }
                // 将处理完的数据push到顶级评论的子集中
                data.childCommentDataLists.push(childData);
            });
            // 存放顶级评论
            that.commentDataList.push(data)
        }
    }
})
...

[!tip] 上面的案例比较适合处理层级关系不深的评论列表,三层及以上则需要去维护一大串的代码逻辑,如果要处理多层,推荐"懒加载"的方式,第一次只查询顶级评论,当用户点击某个评论的下拉列表再次去请求该评论下的子评论,以此类推。

1.1.2. 发布文章(content)类型评论

代码片段

    //vue代码片段
    ...
    // dataType无需传递,后端接口做了限制处理
    form: {
        commentId:'',//父评论编号
        commentContent: '',//评论内容
        dataTitle: '${field.title}',//文章标题
        dataId: '${field.id}',//文章id
        commentPicture: [],// 评论图片JSON
        commentFileJson: [],// 附件JSON
        commentPoints: 0// 评论打分

    },
    ...

    ...
     ms.http.post('/people/comment/save.do', that.form).then(function (res) {
        if (res.result) {
            this.$message({
                title: '成功',
                message: '评论成功',
                type: 'success'
            });
        } else {
            this.$message({
                title: '失败',
                message: '评论失败',
                type: 'error'
            });
        }
    })
    ...

[!tip] 父子评论需注意正确传递commentId参数; 游客模式和会员有一些差异,参考http://doc.mingsoft.net/plugs/ping-lun-cha-jian/jie-shao.html#%E6%8E%A5%E5%8F%A3

1.1.3. 新增或查看其他类型评论,如商品

在新业务模块中前台和会员端的评论接口需要重写;后台的评论接口无需调整,通过前端传递不同的datatype区分。

如下:以保存接口为例,其他接口如查询处理逻辑一致,限制datatype类型即可


<!-- 在新业务模块中调整前端评论保存接口请求地址,其余参数组织与上方发布评论接口一致 -->
 ms.http.post('新业务的接口地址', that.form).then(function (res) {
        if (res.result) {

        } else {

        }
    })
// 后端:在新业务模块中对应的web端或会员端新增接口,限制类型为商品
    public ResultData save(@ModelAttribute @Parameter(hidden = true) CommentEntity comment) {
        // 业务处理
        ···
        // 获取当前模块评论类型,如商品 
        // 注意datatype值为 自定义字典中 评论类型对应的dict_label值 
        comment.setDataType(Const.COMMENT_DATA_TYPE);
        commentBiz.saveComment(comment);
        return ResultData.build().success();
    }

1.1.4. 参数

图片、附件等参数需要json格式,其他参数正常处理即可 参考如下代码片段 代码片段:

    ···
        that.form.commentFileJson.push({
            url: file.url?file.url:file.path,
            name: file.name,
            path: response.data,
            uid: file.uid,
            status: 'success'
        });
        that.form.commentFileJson.push({
            url: file.url?file.url:file.path,
            name: file.name,
            path: response.data,
            uid: file.uid,
            status: 'success'
        });
    ···
    // 将封装好的图片数据转为JSON
    if (that.form.commentPicture && that.form.commentPicture != '[]'){
        that.form.commentPicture = JSON.stringify(that.form.commentPicture)
    }
    // 将封装好的附件数据转为JSON
    if (that.form.commentFileJson && that.form.commentFileJson != '[]'){
        that.form.commentFileJson = JSON.stringify(that.form.commentFileJson)
    }
    ···

1.2. 后端开发

1.2.1. 约定

配置名称约定

各评论数据业务有独立评论配置,评论配置名称约定为 评论类型字典中当前业务的名称 + 评论配置;

如文章的评论配置名称为: 文章评论配置

Alt text

1.2.2. 配置

评论类型字典配置

在自定义模型 => 自定义字典 评论类型字典中 新增业务对应的评论类型名称

Alt text

自定义业务评论配置

下方是自定义配置 评论配置模型json模板,title属性需要按照上面配置名称约定去设置,其余不变

{
    "searchJson": "[\n]\n",
    "field": "[\n    {\n    \"model\":\"enableComment\",\n    \"key\":\"ENABLE_COMMENT\",\n    \"field\":\"ENABLE_COMMENT\",\n    \"javaType\":\"Boolean\",\n    \"jdbcType\":\"VARCHAR\",\n    \"name\":\"开启评论\",\n    \"type\":\"switch\",\n    \"length\":\"11\",\n    \"isShow\":false,\n    \"isNoRepeat\":false,\n    \"isSearch\":false,\n    \"isRequired\":false\n    }\n    ,{\n    \"model\":\"enableVisitor\",\n    \"key\":\"ENABLE_VISITOR\",\n    \"field\":\"ENABLE_VISITOR\",\n    \"javaType\":\"Boolean\",\n    \"jdbcType\":\"VARCHAR\",\n    \"name\":\"开启游客模式\",\n    \"type\":\"switch\",\n    \"length\":\"11\",\n    \"isShow\":false,\n    \"isNoRepeat\":false,\n    \"isSearch\":false,\n    \"isRequired\":false\n    }\n    ,{\n    \"model\":\"enableAudit\",\n    \"key\":\"ENABLE_AUDIT\",\n    \"field\":\"ENABLE_AUDIT\",\n    \"javaType\":\"Boolean\",\n    \"jdbcType\":\"VARCHAR\",\n    \"name\":\"开启审核\",\n    \"type\":\"switch\",\n    \"length\":\"11\",\n    \"isShow\":false,\n    \"isNoRepeat\":false,\n    \"isSearch\":false,\n    \"isRequired\":false\n    }\n]\n\n",
    "html": "\n<template id=\"custom-model\">\n    <el-form ref=\"form\" :model=\"form\" :rules=\"rules\" label-width=\"120px\" label-position=\"right\" size=\"small\" :disabled=\"disabled\" v-loading=\"loading\">\n        <!--开启评论-->\n    \n        <el-form-item  label=\"开启评论\" prop=\"enableComment\">\n            <el-switch v-model=\"form.enableComment\"\n                       :disabled=\"false\">\n            </el-switch>\n                <div class=\"ms-form-tip\">\n开启评论后允许发布评论                </div>\n        </el-form-item>\n   \n        <!--开启游客模式-->\n    \n        <el-form-item  label=\"开启游客模式\" prop=\"enableVisitor\">\n            <el-switch v-model=\"form.enableVisitor\"\n                       :disabled=\"false\">\n            </el-switch>\n                <div class=\"ms-form-tip\">\n开启后,无需登录也能评论;不开启推荐                </div>\n        </el-form-item>\n   \n        <!--开启审核-->\n    \n        <el-form-item  label=\"开启审核\" prop=\"enableAudit\">\n            <el-switch v-model=\"form.enableAudit\"\n                       :disabled=\"false\">\n            </el-switch>\n                <div class=\"ms-form-tip\">\n开启后,评论信息审核通过后才在前台显示                </div>\n        </el-form-item>\n   \n    </el-form>\n</template>\n",
    "title": "商品评论配置",
    "script": "var custom_model = Vue.component(\"custom-model\",{\n    el: '#custom-model',\n    data:function() {\n        return {\n\t\t\tloading:false,\n            disabled:false,\n            modelId:0,\n            modelName: \"评论配置\",\n            //表单数据\n            form: {\n                linkId:0,\n                    // 开启评论\n                    enableComment:true,\n                    // 开启游客模式\n                    enableVisitor:false,\n                    // 开启审核\n                    enableAudit:true,\n            },\n\n            rules:{\n            },\n        }\n    },\n    watch:{\n            \n            //开启评论       \n            \"form.enableComment\":function(nev,old){\n               if(typeof(nev)=='string') {\n                   this.form.enableComment = (nev=='true');\n               } else  if(typeof(nev)=='undefined') {\n                    this.form.enableComment = false;\n               }        \n            },\n            \n            //开启游客模式       \n            \"form.enableVisitor\":function(nev,old){\n               if(typeof(nev)=='string') {\n                   this.form.enableVisitor = (nev=='true');\n               } else  if(typeof(nev)=='undefined') {\n                    this.form.enableVisitor = false;\n               }        \n            },\n            \n            //开启审核       \n            \"form.enableAudit\":function(nev,old){\n               if(typeof(nev)=='string') {\n                   this.form.enableAudit = (nev=='true');\n               } else  if(typeof(nev)=='undefined') {\n                    this.form.enableAudit = false;\n               }        \n            },\n    },\n    components:{\n    },\n    computed:{\n    },\n    methods: {\n      \tlink:function(e,field,binds){\n      \t\tlet that = this;\n            binds.forEach(function(item){\n  \t\t\t\tms.http.post(ms.manager+'/project/form/link.do', {id:that.modelId,field:item.field,value:e}).then(function (res) {\n                    if(res.result && res.data) {\n                        that.form[ms.util.camelCaseString(item.field)]=res.data[0][item.target];\n                    }else{\n                        that.$notify({\n                            title: '失败',\n                            message: res.msg,\n                            type: 'warning'\n                        });\n                    }\n                })\n\n            });\n      \t},\n              update: function (row) {\n                var that = this;\n                ms.http.post(ms.manager+\"/comment/commentConfig/update.do\", row).then(function (data) {\n                  if (data.result) {\n                    that.$notify({\n                      title: '成功',\n                      message: '更新成功',\n                      type: 'success'\n                    });\n\n                  } else {\n                    that.$notify({\n                      title: '失败',\n                      message: data.msg,\n                      type: 'warning'\n                    });\n                  }\n                });\n              },         validate:function(){\n            var b = false\n            this.$refs.form.validate(function(valid){\n                b = valid;\n            });\n            return b;\n        },\n        getFormData() {\n            var that = this;\n            var form = JSON.parse(JSON.stringify(that.form));\n            form.modelId = that.modelId;\n            return form;\n        },\n        save:function(callback) {\n            var that = this;\n            var url = this.formURL.save.url;\n            if (that.form.id > 0) {\n                url = this.formURL.update.url;\n            }\n            this.$refs.form.validate(function(valid) {\n                if (valid) {\n                    var form = JSON.parse(JSON.stringify(that.form));\n                    form.modelId = that.modelId;\n                    ms.http.post(url, form).then(function (res) {\n                        if(callback) {\n                            callback(res);\n                        }\n                    }).catch(function(err){\n                        callback(err.response.data);\n                    });\n                } else{\n                    callback({\n                    result:false,msg:'请检查表单输入项'\n                    });\n                }\n            })\n        },\n        //获取当前评论配置\n        get:function(id) {\n            var that = this;\n            that.loading = true;\n            ms.http.get(this.formURL.get.url, Object.assign({\"modelId\":that.modelId},this.formURL.get.params)).then(function (res) {\n                if(res.result&&res.data){\n                    that.form = res.data;\n                    that.loading = false;\n                } else {\n                    that.loading = false;\n                }\n            }).catch(function (err) {\n                console.log(err);\n                that.loading = false;\n            });\n        },\n\n    },\n    created:function() {\n        var that = this;\n        //渲染create\n        that.get(this.form.linkId);\n    }\n});",
    "tableName": "COMMENT_CONFIG"
}

Alt text

自定义配置中导入配置模型json

Alt text

在业务菜单增加业务评论管理权限

如文章管理下管理文章评论,需要在菜单增加功能权限

comment:commentData:业务数据对应评论类型的数据值:view(reply|audit|del)

Alt text

业务数据页面的操作列增加 回复列表 操作按钮

如文章管理的列表页增加回复列表,示例如下

Alt text

...
<!-- 页面部分 操作列增加 回复列表 -->

<el-link v-if="ms.util.includes(ms.managerPermissions,'comment:commentData:'+commentDataType+':view') || ms.util.includes(ms.managerPermissions,'comment:comment:view')" type="primary" :underline="false" @click="viewComment(scope.row.id)" >回复列表</el-link>

...
<!-- vue data部分 定义commentDataType -->
data: function(){
    return {
        ...
        commentDataType: "content",
    }
}

<!--vue method 增加viewComment实现-->
viewComment:function (id){
    var that = this;
    var backUrl = encodeURIComponent("/leader/box/index.do");
    ms.util.openSystemUrl("/comment/data/index.do?dataType="+that.commentDataType+"&backUrl="+backUrl+"&dataId="+id);
},

点击回复列表查看该文章的评论数据列表

Alt text

1.2.3. 前台、会员层添加评论

参考comment模块 action /web/CommentAction|/people/CommentAction

1.2.4. 通用评论保存业务

在业务开发时注意查看biz.ICommentBiz中的方法注释,有些参数校验或者字段默认赋值等操作已经在业务层统一处理,评论的dataType保存在数据库存储的都是字典的value值;

下面以业务层saveComment为例 biz.impl.commentBizImpl中saveComment方法代码片段

··· 基本数据校验    
    // 判断评论类型
    String dataLabel = DictUtil.getDictLabel("评论类型", comment.getDataType());
    if (StringUtils.isBlank(dataLabel)){
        throw new BusinessException(BundleUtil.getBaseString("err.error",
                BundleUtil.getString(net.mingsoft.comment.constant.Const.RESOURCES,"comment.type")));
    }

    // 是否开启评论
    // todo 规定在各个子业务模块定义对应的评论配置,评论配置命名为 dataType在自定义字典中评论类型对应的dictLabel值,如文章 +"评论配置"
    String configName  = dataLabel + "评论配置";
    if (!ConfigUtil.getBoolean(configName,"enableComment")){
        throw new BusinessException(BundleUtil.getBaseString("fail",
                BundleUtil.getString(net.mingsoft.comment.constant.Const.RESOURCES,"comment")));
    }
···

    // 涉及评论是否审核,开启取到配置为true,需要取反,false为未审核
    comment.setCommentAudit(!ConfigUtil.getBoolean("评论配置", "enableAudit"));
    // 评论ip
    HashMap<String, String> map = new HashMap<>();
    map.put("ipv4",BasicUtil.getIp());
    map.put("addr",IpUtils.getRealAddressByIp(BasicUtil.getIp()));
    comment.setCommentIp(JSONUtil.toJsonStr(map));
    //评论时间
    comment.setCommentTime(new Date());
    comment.setUpdateDate(new Date());
    comment.setCreateDate(new Date());
    // 初始化评论点赞数
    comment.setCommentLike(0);
    // 附件及图片
    if (StringUtils.isBlank(comment.getCommentPicture()) || !comment.getCommentPicture().matches("^\\[.{1,}]$")) {
        comment.setCommentPicture("");
    }
    if (StringUtils.isBlank(comment.getCommentFileJson()) || !comment.getCommentFileJson().matches("^\\[.{1,}]$")) {
        comment.setCommentFileJson("");
    }
    // 设置顶级评论id  判断当前回复的评论是否是顶级评论,不是则将top_id设置为当前回复评论的top_id
    setTopId(comment);

    return commentDao.insert(comment);

1.2.5. 保存评论

保存时需注意,一些参数需要按指定格式传参,如peopleInfo字段需要json结构。

处理完参数调用统一的业务层方法即可。

下面是action.people.commentAction save方法的代码片段:

    // 判断登陆设置peopleId
    PeopleEntity people = this.getPeopleBySession();
    PeopleUserEntity peopleUserEntity = (PeopleUserEntity) peopleUserBiz.getEntity(people.getIntId());
    // 设置会员相关信息
    if (peopleUserEntity != null){
        Map commentPeopleInfo = new HashMap();
        // 这里可以根据业务需求填充用户数据
        commentPeopleInfo.put("puIcon",peopleUserEntity.getPuIcon());
        String peopleInfo = JSONUtil.toJsonStr(commentPeopleInfo);
        comment.setPeopleInfo(peopleInfo);

        comment.setPeopleName(peopleUserEntity.getPuNickname());
        comment.setPeopleId(peopleUserEntity.getPeopleId());
    }
    // 获取当前模块评论类型
    comment.setDataType(Const.COMMENT_DATA_TYPE);
    // 业务层再对评论数据做处理 如 ip、时间、评论关系等
    commentBiz.saveComment(comment);

1.2.6. 删除评论

后台管理可以批量删除评论,默认代码中前台只允许用户删除自己的评论,注意如果重写action的代码要对用户的id进行校验

代码参考:

    PeopleEntity people = this.getPeopleBySession();
    if (StringUtils.isBlank(id)){
        return ResultData.build().error(getResString("err.not.exist",this.getResString("comment")));
    }
    CommentEntity comment = commentBiz.getById(id);
    if (comment==null || !people.getId().equals(comment.getPeopleId())){
            return ResultData.build().error(getResString("err.not.exist",this.getResString("comment")));
    }

级联删除: 从产品角度来讲,可以允许父评论删除后保留子评论,增加逻辑删除业务,界面则不显示评论内容; 如果需要级联删除业务,建议在数据库设置外键关联,在数据库层面级联删除评论

附件、图片等处理:推荐使用我们的清理插件,可以清理一些删除评论后遗留的无用文件http://doc.mingsoft.net/plugs/qing-li-cha-jian/jie-shao.html

在默认代码中,评论log表中统计数量会随评论表中记录数增加,但是不会随着删除而减少,这是我们觉得比较合理的一个设计;当然如果并不需要这么做的话,请在删除时参考如下代码处理记录数。

    //查询评论记录是否有该评论
    CommentsLogEntity data = commentsLogBiz.getOne(new QueryWrapper<CommentsLogEntity>().eq("data_id",dataId).eq("data_type",dataType));
    //评论数-1
    data.setCommentsCount(data.getCommentsCount()-1);
    commentsLogBiz.updateById(data);

1.2.7. 修改评论

不支持修改评论

1.2.8. 查询评论

建议调用web端查询接口,people目录下的action接口需要会员登陆

web代码参考:

    //只显示审核通过的评论
    comment.setCommentAudit(true);
    // 确保第一次只查询出父评论
    BasicUtil.startPage();
    List<CommentEntity> list = commentBiz.query(comment);
    return ResultData.build().success().data(new EUListBean(list,(int)BasicUtil.endPage(list).getTotal()));

1.2.9. 业务对接

在开发时,对接其他业务如文章评论、商品评论等

在这些业务删除时,可以考虑aop处理同时删除相关联的评论数据

Copyright © mingsoft.net 2012-2022 all right reserved,powered by Gitbook该文件修订时间: 2025-08-27 09:42:01

results matching ""

    No results matching ""

    results matching ""

      No results matching ""