parent
99ca85c0dc
commit
1e87845534
@ -0,0 +1,264 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
pb "moredoc/api/v1"
|
||||
"moredoc/middleware/auth"
|
||||
"moredoc/model"
|
||||
"moredoc/util"
|
||||
"moredoc/util/captcha"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CommentAPIService struct {
|
||||
pb.UnimplementedCommentAPIServer
|
||||
dbModel *model.DBModel
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewCommentAPIService(dbModel *model.DBModel, logger *zap.Logger) (service *CommentAPIService) {
|
||||
return &CommentAPIService{dbModel: dbModel, logger: logger.Named("CommentAPIService")}
|
||||
}
|
||||
|
||||
func (s *CommentAPIService) checkLogin(ctx context.Context) (*auth.UserClaims, error) {
|
||||
return checkGRPCLogin(s.dbModel, ctx)
|
||||
}
|
||||
|
||||
func (s *CommentAPIService) checkPermission(ctx context.Context) (*auth.UserClaims, error) {
|
||||
return checkGRPCPermission(s.dbModel, ctx)
|
||||
}
|
||||
|
||||
// 发表评论
|
||||
func (s *CommentAPIService) CreateComment(ctx context.Context, req *pb.CreateCommentRequest) (*emptypb.Empty, error) {
|
||||
userClaims, err := checkGRPCLogin(s.dbModel, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 评论验证码错误
|
||||
cfg := s.dbModel.GetConfigOfSecurity(model.ConfigSecurityEnableCaptchaComment)
|
||||
if cfg.EnableCaptchaComment && !captcha.VerifyCaptcha(req.CaptchaId, req.Captcha) {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "验证码错误")
|
||||
}
|
||||
|
||||
comment := &model.Comment{}
|
||||
err = util.CopyStruct(req, comment)
|
||||
if err != nil {
|
||||
s.logger.Error("CreateDocument", zap.Error(err))
|
||||
return nil, status.Errorf(codes.Internal, "发布评论失败:"+err.Error())
|
||||
}
|
||||
|
||||
if comment.DocumentId <= 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "文档id不能为空")
|
||||
}
|
||||
|
||||
if comment.Content == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "评论内容不能为空")
|
||||
}
|
||||
|
||||
defaultStatus, err := s.dbModel.CanIPublishComment(userClaims.UserId)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.PermissionDenied, err.Error())
|
||||
}
|
||||
|
||||
comment.Status = defaultStatus
|
||||
comment.UserId = userClaims.UserId
|
||||
err = s.dbModel.CreateComment(comment)
|
||||
if err != nil {
|
||||
s.logger.Error("CreateComment", zap.Error(err))
|
||||
return nil, status.Errorf(codes.Internal, "发布评论失败:"+err.Error())
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
// 更新评论,仅限管理员
|
||||
func (s *CommentAPIService) UpdateComment(ctx context.Context, req *pb.Comment) (*emptypb.Empty, error) {
|
||||
userClaims, err := s.checkPermission(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Debug("UpdateComment", zap.Any("user", userClaims), zap.Any("req", req))
|
||||
|
||||
// 只允许更新评论状态和内容
|
||||
updateFields := []string{"content", "status"}
|
||||
comment := &model.Comment{}
|
||||
util.CopyStruct(req, comment)
|
||||
err = s.dbModel.UpdateComment(comment, updateFields...)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "更新评论失败")
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
// 登录的用户,可删除自己的评论
|
||||
// 管理员,可删除任意评论
|
||||
func (s *CommentAPIService) DeleteComment(ctx context.Context, req *pb.DeleteCommentRequest) (*emptypb.Empty, error) {
|
||||
var userIds []int64
|
||||
userClaims, err := s.checkPermission(ctx)
|
||||
if err != nil && userClaims == nil {
|
||||
// 未登录用户
|
||||
return nil, err
|
||||
}
|
||||
// 已登录用户,判断有管理员权限
|
||||
isAdmin := userClaims.UserId > 0 && err == nil
|
||||
if !isAdmin {
|
||||
// 非管理员,限定只能删除自己的评论
|
||||
userIds = append(userIds, userClaims.UserId)
|
||||
}
|
||||
|
||||
err = s.dbModel.DeleteComment(req.Id, userIds...)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "删除评论失败")
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *CommentAPIService) GetComment(ctx context.Context, req *pb.GetCommentRequest) (*pb.Comment, error) {
|
||||
_, err := s.checkPermission(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.logger.Debug("GetComment", zap.Any("req", req))
|
||||
comment, err := s.dbModel.GetComment(req.Id)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "获取评论失败:"+err.Error())
|
||||
}
|
||||
user, _ := s.dbModel.GetUser(comment.UserId, model.UserPublicFields...)
|
||||
|
||||
pbComment := &pb.Comment{}
|
||||
util.CopyStruct(comment, pbComment)
|
||||
util.CopyStruct(user, pbComment.User)
|
||||
|
||||
return pbComment, nil
|
||||
}
|
||||
|
||||
func (s *CommentAPIService) ListComment(ctx context.Context, req *pb.ListCommentRequest) (*pb.ListCommentReply, error) {
|
||||
var (
|
||||
isLogin bool
|
||||
isAdmin bool
|
||||
)
|
||||
|
||||
userClaims, err := s.checkPermission(ctx)
|
||||
if err != nil && userClaims == nil {
|
||||
// 未登录用户
|
||||
isLogin = false
|
||||
} else {
|
||||
// 已登录用户,判断有管理员权限
|
||||
isLogin = true
|
||||
isAdmin = userClaims.UserId > 0 && err == nil
|
||||
}
|
||||
|
||||
opt := &model.OptionGetCommentList{
|
||||
Page: int(req.Page),
|
||||
Size: int(req.Size_),
|
||||
WithCount: true,
|
||||
QueryIn: make(map[string][]interface{}),
|
||||
QueryLike: make(map[string][]interface{}),
|
||||
Sort: []string{req.Order},
|
||||
}
|
||||
|
||||
// default status
|
||||
opt.QueryIn["status"] = []interface{}{model.CommentStatusApproved}
|
||||
|
||||
if req.DocumentId > 0 {
|
||||
opt.QueryIn["document_id"] = []interface{}{req.DocumentId}
|
||||
opt.Page = 1
|
||||
opt.Size = 1000000
|
||||
}
|
||||
|
||||
if len(req.ParentId) > 0 {
|
||||
opt.QueryIn["parent_id"] = util.Slice2Interface(req.ParentId)
|
||||
}
|
||||
|
||||
if isAdmin && req.Wd != "" {
|
||||
opt.QueryLike["content"] = []interface{}{req.Wd}
|
||||
}
|
||||
|
||||
if (isLogin && req.UserId == userClaims.UserId) || isAdmin {
|
||||
delete(opt.QueryIn, "status")
|
||||
if len(req.Status) > 0 {
|
||||
opt.QueryIn["status"] = util.Slice2Interface(req.Status)
|
||||
}
|
||||
}
|
||||
|
||||
if req.UserId > 0 {
|
||||
opt.QueryIn["user_id"] = []interface{}{req.UserId}
|
||||
}
|
||||
|
||||
comments, total, err := s.dbModel.GetCommentList(opt)
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return nil, status.Errorf(codes.Internal, "获取评论列表失败")
|
||||
}
|
||||
|
||||
resp := &pb.ListCommentReply{
|
||||
Total: total,
|
||||
}
|
||||
util.CopyStruct(comments, &resp.Comment)
|
||||
|
||||
var (
|
||||
userIds []int64
|
||||
documentIds []int64
|
||||
userIdMapCommentIdx = make(map[int64][]int)
|
||||
documentIdMapCommentIdx = make(map[int64][]int)
|
||||
)
|
||||
for idx, comment := range comments {
|
||||
userIds = append(userIds, comment.UserId)
|
||||
documentIds = append(documentIds, comment.DocumentId)
|
||||
userIdMapCommentIdx[comment.UserId] = append(userIdMapCommentIdx[comment.UserId], idx)
|
||||
documentIdMapCommentIdx[comment.DocumentId] = append(documentIdMapCommentIdx[comment.DocumentId], idx)
|
||||
}
|
||||
|
||||
if len(userIds) > 0 {
|
||||
users, _, _ := s.dbModel.GetUserList(&model.OptionGetUserList{
|
||||
SelectFields: model.UserPublicFields,
|
||||
WithCount: false,
|
||||
QueryIn: map[string][]interface{}{"id": util.Slice2Interface(userIds)},
|
||||
})
|
||||
|
||||
for _, user := range users {
|
||||
indexes := userIdMapCommentIdx[user.Id]
|
||||
for _, idx := range indexes {
|
||||
util.CopyStruct(user, &resp.Comment[idx].User)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(documentIds) > 0 {
|
||||
documents, _, _ := s.dbModel.GetDocumentList(&model.OptionGetDocumentList{
|
||||
SelectFields: []string{"id", "title"},
|
||||
WithCount: false,
|
||||
QueryIn: map[string][]interface{}{"id": util.Slice2Interface(documentIds)},
|
||||
})
|
||||
|
||||
for _, document := range documents {
|
||||
indexes := documentIdMapCommentIdx[document.Id]
|
||||
for _, idx := range indexes {
|
||||
resp.Comment[idx].DocumentTitle = document.Title
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *CommentAPIService) CheckComment(ctx context.Context, req *pb.CheckCommentRequest) (*emptypb.Empty, error) {
|
||||
_, err := s.checkPermission(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.dbModel.UpdateCommentStatus(req.Id, req.Status)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import service from '~/utils/request'
|
||||
|
||||
export const createComment = (data) => {
|
||||
return service({
|
||||
url: '/api/v1/comment',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export const updateComment = (data) => {
|
||||
return service({
|
||||
url: '/api/v1/comment',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteComment = (params) => {
|
||||
return service({
|
||||
url: '/api/v1/comment',
|
||||
method: 'delete',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
export const getComment = (params) => {
|
||||
return service({
|
||||
url: '/api/v1/comment',
|
||||
method: 'get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
export const listComment = (params) => {
|
||||
return service({
|
||||
url: '/api/v1/comment/list',
|
||||
method: 'get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
export const checkComment = (data) => {
|
||||
return service({
|
||||
url: '/api/v1/comment/check',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<el-row :class="`com-comment-item com-comment-item-${size}`">
|
||||
<el-col :span="2">
|
||||
<nuxt-link :to="{ name: 'user-id', params: { id: user.id } }"
|
||||
><user-avatar :size="size == 'small' ? 40 : 48" :user="comment.user"
|
||||
/></nuxt-link>
|
||||
</el-col>
|
||||
<el-col :span="22">
|
||||
<div class="username">
|
||||
<nuxt-link
|
||||
class="el-link el-link--default"
|
||||
:to="{ name: 'user-id', params: { id: comment.user_id } }"
|
||||
>{{ comment.user.username }}</nuxt-link
|
||||
>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-html="comment.reply_user" />
|
||||
{{ comment.content }}
|
||||
</div>
|
||||
<div class="comment-action">
|
||||
<el-row class="help-block">
|
||||
<el-col :span="12">
|
||||
<el-tooltip
|
||||
:content="formatDatetime(comment.created_at)"
|
||||
placement="right"
|
||||
>
|
||||
<span class="text-muted">
|
||||
<i class="el-icon-time"></i>
|
||||
{{ formatRelativeTime(comment.created_at) }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="12" class="text-right">
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
icon="el-icon-chat-dot-square"
|
||||
@click="reply"
|
||||
>回复</el-button
|
||||
>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<form-comment
|
||||
v-if="replyComment"
|
||||
:article-id="comment.article_id"
|
||||
:parent-id="comment.id"
|
||||
:placeholder="`回复 ${comment.user.username}`"
|
||||
@success="commentSuccess"
|
||||
/>
|
||||
<slot></slot>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import UserAvatar from '~/components/UserAvatar.vue'
|
||||
import { formatRelativeTime, formatDatetime } from '~/utils/utils'
|
||||
|
||||
export default {
|
||||
name: 'CommentItem',
|
||||
components: { UserAvatar },
|
||||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default', // default、small
|
||||
},
|
||||
comment: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
id: 0,
|
||||
parent_id: 0,
|
||||
user_id: 0,
|
||||
username: '匿名',
|
||||
avatar: '',
|
||||
group_id: 0,
|
||||
verify_status: 0,
|
||||
content: '内容加载中...',
|
||||
created_at: '0000-00-00',
|
||||
}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
replyComment: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('user', ['user']),
|
||||
},
|
||||
methods: {
|
||||
formatRelativeTime,
|
||||
formatDatetime,
|
||||
reply() {
|
||||
this.replyComment = !this.replyComment
|
||||
},
|
||||
commentSuccess() {
|
||||
this.$emit('success')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.com-comment-item {
|
||||
font-size: 14px;
|
||||
.comment-content {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #f5f7f8;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
color: #565656;
|
||||
span {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
}
|
||||
.username a {
|
||||
font-weight: 400;
|
||||
color: #222;
|
||||
}
|
||||
}
|
||||
.com-comment-item-small {
|
||||
font-size: 13px;
|
||||
.el-col-2 {
|
||||
width: 7%;
|
||||
}
|
||||
.el-col-22 {
|
||||
width: 93%;
|
||||
}
|
||||
.comment-content {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div class="com-comment-list">
|
||||
<comment-item
|
||||
v-for="comment in comments"
|
||||
:key="'comment-' + comment.id"
|
||||
:comment="comment"
|
||||
@success="commentSuccess"
|
||||
>
|
||||
<comment-item
|
||||
v-for="child in comment.children"
|
||||
:key="'comment-' + child.id"
|
||||
:comment="child"
|
||||
:size="'small'"
|
||||
@success="commentSuccess"
|
||||
></comment-item>
|
||||
</comment-item>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import CommentItem from '~/components/CommentItem.vue'
|
||||
import { listComment } from '~/api/comment'
|
||||
|
||||
export default {
|
||||
name: 'CommentList',
|
||||
components: { CommentItem },
|
||||
props: {
|
||||
articleId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
parentId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
comments: [],
|
||||
req: {
|
||||
article_id: this.articleId,
|
||||
parent_id: this.parentId,
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('user', ['user']),
|
||||
},
|
||||
watch: {
|
||||
articleId: {
|
||||
handler(val) {
|
||||
this.req.article_id = val
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
parentId: {
|
||||
handler(val) {
|
||||
this.req.parent_id = val
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getComments()
|
||||
},
|
||||
methods: {
|
||||
// 获取文章评论列表
|
||||
async getComments() {
|
||||
if (!this.req.article_id) return
|
||||
const res = await listComment({
|
||||
article_id: this.articleId,
|
||||
order: 'id asc',
|
||||
})
|
||||
if (res.status === 200) {
|
||||
this.comments = this.comments2tree(res.data.comment || [])
|
||||
}
|
||||
},
|
||||
commentSuccess() {
|
||||
this.getComments()
|
||||
},
|
||||
comments2tree(comments) {
|
||||
const tree = []
|
||||
const map = {}
|
||||
comments.forEach((comment) => {
|
||||
map[comment.id] = comment
|
||||
})
|
||||
comments.forEach((comment) => {
|
||||
// 寻找最顶层父级
|
||||
let parent = map[comment.parent_id]
|
||||
let replyUser = ''
|
||||
if (comment.parent_id && parent) {
|
||||
try {
|
||||
replyUser = `<a href="/user/${parent.user.id}" class="el-link el-link--primary" target="blank">@${parent.user.username}</a>`
|
||||
} catch (error) {}
|
||||
}
|
||||
while (parent && parent.parent_id) {
|
||||
parent = map[parent.parent_id]
|
||||
}
|
||||
comment.reply_user = replyUser
|
||||
if (parent) {
|
||||
;(parent.children || (parent.children = [])).push(comment)
|
||||
} else {
|
||||
tree.push(comment)
|
||||
}
|
||||
})
|
||||
return tree
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.com-comment-list {
|
||||
& > .el-row {
|
||||
margin-top: 20px;
|
||||
border-bottom: 1px solid #efefef;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
& > .el-row:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="com-form-comment-check">
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="icomment"
|
||||
class="form-comment-check"
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item prop="content" label="评论内容">
|
||||
<el-input
|
||||
v-model="icomment.content"
|
||||
type="textarea"
|
||||
:placeholder="placeholder"
|
||||
:autosize="{ minRows: 4, maxRows: 6 }"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核状态">
|
||||
<el-radio-group v-model="icomment.status">
|
||||
<el-radio :label="0">待审核</el-radio>
|
||||
<el-radio :label="1">审核通过</el-radio>
|
||||
<el-radio :label="2">审核拒绝</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="btn-block"
|
||||
icon="el-icon-check"
|
||||
@click="onSubmit"
|
||||
>提交</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { updateComment } from '~/api/comment'
|
||||
export default {
|
||||
name: 'FormCommentCheck',
|
||||
props: {
|
||||
comment: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
icomment: {
|
||||
id: 0,
|
||||
content: '',
|
||||
status: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
comment: {
|
||||
handler(val) {
|
||||
this.icomment = { status: 0, ...val }
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async onSubmit() {
|
||||
const res = await updateComment(this.icomment)
|
||||
if (res.status === 200) {
|
||||
this.$message.success('更新成功')
|
||||
this.$emit('success')
|
||||
} else {
|
||||
this.$message.error(res.data.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss"></style>
|
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="com-user-avatar">
|
||||
<el-avatar
|
||||
:size="size"
|
||||
:alt="user.username"
|
||||
:src="user.avatar"
|
||||
@error="errorAvatar"
|
||||
>
|
||||
<img src="/static/images/avatar.png" />
|
||||
</el-avatar>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'UserAvatar',
|
||||
props: {
|
||||
size: {
|
||||
type: Number,
|
||||
default: 80,
|
||||
},
|
||||
src: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
user: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
errorAvatar() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.com-user-avatar {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
.el-avatar {
|
||||
border: 2px solid #ddd;
|
||||
padding: 3px;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
&:hover {
|
||||
border: 2px solid #409eff;
|
||||
}
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,16 +1,271 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>{{ $route.name }}</div>
|
||||
<el-card shadow="never" class="search-card">
|
||||
<FormSearch
|
||||
:fields="searchFormFields"
|
||||
:loading="loading"
|
||||
:show-create="false"
|
||||
:show-delete="true"
|
||||
:disabled-delete="selectedRow.length === 0"
|
||||
@onSearch="onSearch"
|
||||
@onCreate="onCreate"
|
||||
@onDelete="batchDelete"
|
||||
>
|
||||
<template slot="buttons">
|
||||
<el-dropdown
|
||||
:disabled="selectedRow.length === 0"
|
||||
@command="checkComment"
|
||||
>
|
||||
<el-button type="primary">
|
||||
批量审批 <i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item :command="1">审核通过</el-dropdown-item>
|
||||
<el-dropdown-item :command="2">审核拒绝</el-dropdown-item>
|
||||
<el-dropdown-item :command="0">变为待审</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</FormSearch>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="mgt-20px">
|
||||
<TableList
|
||||
:loading="loading"
|
||||
:table-data="comments"
|
||||
:fields="tableListFields"
|
||||
:show-actions="true"
|
||||
:show-view="false"
|
||||
:show-edit="true"
|
||||
:show-delete="true"
|
||||
:show-select="true"
|
||||
:actions-min-width="160"
|
||||
@selectRow="selectRow"
|
||||
@editRow="editRow"
|
||||
@deleteRow="deleteRow"
|
||||
/>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="mgt-20px">
|
||||
<div class="text-right">
|
||||
<el-pagination
|
||||
background
|
||||
:current-page="search.page"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="search.size"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
>
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-dialog
|
||||
v-if="comment.id > 0"
|
||||
title="评论编审"
|
||||
:visible.sync="formCommentVisible"
|
||||
width="640px"
|
||||
>
|
||||
<FormCommentCheck
|
||||
ref="formComment"
|
||||
:comment="comment"
|
||||
@success="formCommentSuccess"
|
||||
/>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listComment,
|
||||
deleteComment,
|
||||
getComment,
|
||||
checkComment,
|
||||
} from '~/api/comment'
|
||||
import TableList from '~/components/TableList.vue'
|
||||
import FormSearch from '~/components/FormSearch.vue'
|
||||
export default {
|
||||
components: { TableList, FormSearch },
|
||||
layout: 'admin',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
formCommentVisible: false,
|
||||
search: {
|
||||
wd: '',
|
||||
page: 1,
|
||||
status: [],
|
||||
size: 10,
|
||||
order: 'id desc',
|
||||
},
|
||||
comments: [],
|
||||
total: 0,
|
||||
searchFormFields: [],
|
||||
tableListFields: [],
|
||||
selectedRow: [],
|
||||
comment: { id: 0 },
|
||||
}
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: `面板 - MOREDOC · 魔刀文库`,
|
||||
title: `评论管理 - ${this.settings.system.sitename}`,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return this.$store.state.setting.settings
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
this.initSearchForm()
|
||||
this.initTableListFields()
|
||||
await this.listComment()
|
||||
},
|
||||
methods: {
|
||||
async listComment() {
|
||||
this.loading = true
|
||||
const res = await listComment(this.search)
|
||||
if (res.status === 200) {
|
||||
this.comments = (res.data.comment || []).map((item) => {
|
||||
item.username = item.user.username
|
||||
return item
|
||||
})
|
||||
this.total = res.data.total
|
||||
} else {
|
||||
this.$message.error(res.data.message)
|
||||
}
|
||||
this.loading = false
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.search.size = val
|
||||
this.listComment()
|
||||
},
|
||||
handlePageChange(val) {
|
||||
this.search.page = val
|
||||
this.listComment()
|
||||
},
|
||||
onSearch(search) {
|
||||
this.search = { ...this.search, page: 1, ...search }
|
||||
this.listComment()
|
||||
},
|
||||
onCreate() {
|
||||
this.comment = { id: 0 }
|
||||
this.formCommentVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.commentForm.reset()
|
||||
})
|
||||
},
|
||||
async editRow(row) {
|
||||
const res = await getComment({ id: row.id })
|
||||
if (res.status === 200) {
|
||||
this.comment = res.data
|
||||
this.formCommentVisible = true
|
||||
} else {
|
||||
this.$message.error(res.data.message)
|
||||
}
|
||||
},
|
||||
formCommentSuccess() {
|
||||
this.formCommentVisible = false
|
||||
this.listComment()
|
||||
},
|
||||
async checkComment(cmd) {
|
||||
const res = await checkComment({
|
||||
id: this.selectedRow.map((item) => item.id),
|
||||
status: cmd,
|
||||
})
|
||||
if (res.status === 200) {
|
||||
this.$message.success('审批成功')
|
||||
this.listComment()
|
||||
return
|
||||
}
|
||||
this.$message.error(res.data.message || '审批失败')
|
||||
},
|
||||
batchDelete() {
|
||||
this.$confirm(
|
||||
`您确定要删除选中的【${this.selectedRow.length}条】评论吗?删除之后不可恢复!`,
|
||||
'温馨提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(async () => {
|
||||
const ids = this.selectedRow.map((item) => item.id)
|
||||
const res = await deleteComment({ id: ids })
|
||||
if (res.status === 200) {
|
||||
this.$message.success('删除成功')
|
||||
this.listComment()
|
||||
} else {
|
||||
this.$message.error(res.data.message)
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
deleteRow(row) {
|
||||
this.$confirm(`您确定要删除该评论吗?删除之后不可恢复!`, '温馨提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(async () => {
|
||||
const res = await deleteComment({ id: row.id })
|
||||
if (res.status === 200) {
|
||||
this.$message.success('删除成功')
|
||||
this.listComment()
|
||||
} else {
|
||||
this.$message.error(res.data.message)
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
selectRow(rows) {
|
||||
this.selectedRow = rows
|
||||
},
|
||||
initSearchForm() {
|
||||
this.searchFormFields = [
|
||||
{
|
||||
type: 'text',
|
||||
label: '关键字',
|
||||
name: 'wd',
|
||||
placeholder: '请输入关键字',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
label: '状态',
|
||||
name: 'status',
|
||||
placeholder: '请选择状态',
|
||||
multiple: true,
|
||||
options: [
|
||||
{ label: '审核拒绝', value: 2 },
|
||||
{ label: '审核通过', value: 1 },
|
||||
{ label: '待审核', value: 0 },
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
initTableListFields() {
|
||||
this.tableListFields = [
|
||||
{ prop: 'id', label: 'ID', width: 80, type: 'number', fixed: 'left' },
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 80,
|
||||
type: 'enum',
|
||||
fixed: 'left',
|
||||
enum: {
|
||||
2: { label: '审核拒绝', value: 2, type: 'danger' },
|
||||
1: { label: '审核通过', value: 1, type: 'success' },
|
||||
0: { label: '待审核', value: 0 },
|
||||
},
|
||||
},
|
||||
{ prop: 'document_title', label: '文档', minWidth: 150 },
|
||||
{ prop: 'content', label: '评论内容', minWidth: 150 },
|
||||
{ prop: 'username', label: '评论人', minWidth: 150 },
|
||||
{ prop: 'created_at', label: '创建时间', width: 160, type: 'datetime' },
|
||||
{ prop: 'updated_at', label: '更新时间', width: 160, type: 'datetime' },
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
|
Loading…
Reference in new issue