【新增】反馈管理

This commit is contained in:
2025-11-04 10:06:20 +08:00
parent 80a60e37af
commit 73dda8b648
9 changed files with 302 additions and 45 deletions

View File

@@ -0,0 +1,11 @@
package com.corewing.app.dto.biz.feedback;
import lombok.Data;
import java.util.List;
@Data
public class FeedbackBatchDeleteRequest {
private List<Long> ids;
}

View File

@@ -0,0 +1,13 @@
package com.corewing.app.dto.biz.feedback;
import lombok.Data;
import java.util.List;
@Data
public class FeedbackBatchStatusRequest {
private List<Long> ids;
private Integer status;
}

View File

@@ -66,4 +66,34 @@ public class Feedback implements Serializable {
*/ */
@TableField(fill = FieldFill.INSERT_UPDATE) @TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime; private LocalDateTime updateTime;
/**
* 用户昵称
*/
@TableField(exist = false)
private String nickName;
/**
* 用户名
*/
@TableField(exist = false)
private String username;
@TableField(exist = false)
private String statusName;
public String getStatusName() {
switch (status) {
case 0:
return "待处理";
case 1:
return "处理中";
case 2:
return "已完成";
case 3:
return "已关闭";
default:
return "";
}
}
} }

View File

@@ -1,8 +1,10 @@
package com.corewing.app.mapper; package com.corewing.app.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.entity.Feedback; import com.corewing.app.entity.Feedback;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/** /**
* 问题反馈 Mapper 接口 * 问题反馈 Mapper 接口
@@ -10,4 +12,5 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper @Mapper
public interface FeedbackMapper extends BaseMapper<Feedback> { public interface FeedbackMapper extends BaseMapper<Feedback> {
Page<Feedback> page(Page<Feedback> page, @Param("feedback") Feedback feedback);
} }

View File

@@ -0,0 +1,101 @@
package com.corewing.app.modules.admin.biz;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.common.Result;
import com.corewing.app.dto.biz.feedback.FeedbackBatchDeleteRequest;
import com.corewing.app.dto.biz.feedback.FeedbackBatchStatusRequest;
import com.corewing.app.entity.Feedback;
import com.corewing.app.service.FeedbackService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 用户反馈
*/
@Controller
@RequestMapping("/biz/feedback")
public class BizFeedBackController {
@Resource
private FeedbackService feedbackService;
/**
* 反馈管理首页
* @return
*/
@GetMapping("/index")
public String index() {
return "admin/biz/feedback/index";
}
/**
* 查询反馈数据分页
* @param feedback
* @return
*/
@GetMapping("/page")
@ResponseBody
public Result<Page<Feedback>> page(Feedback feedback) {
return Result.success(feedbackService.page(feedback));
}
/**
* 新增反馈
* @param feedback
* @return
*/
@PostMapping("/save")
@ResponseBody
public Result<String> save(@RequestBody Feedback feedback) {
return Result.isBool(feedbackService.save(feedback));
}
/**
* 编辑反馈
* @param feedback
* @return
*/
@PostMapping("/update")
@ResponseBody
public Result<String> update(@RequestBody Feedback feedback) {
return Result.isBool(feedbackService.updateById(feedback));
}
/**
* 删除反馈
* @param id
* @return
*/
@DeleteMapping("/delete")
@ResponseBody
public Result<String> delete(Long id) {
return Result.isBool(feedbackService.removeById(id));
}
/**
* 批量删除反馈
* @param feedbackBatchDeleteRequest
* @return
*/
@PostMapping("/batchDelete")
@ResponseBody
public Result<String> batchDelete(@RequestBody FeedbackBatchDeleteRequest feedbackBatchDeleteRequest) {
return Result.isBool(feedbackService.removeBatchByIds(feedbackBatchDeleteRequest.getIds()));
}
/**
* 批量更改状态
* @param feedbackBatchStatusRequest
* @return
*/
@PostMapping("/batchStatus")
@ResponseBody
public Result<String> batchStatus(@RequestBody FeedbackBatchStatusRequest feedbackBatchStatusRequest) {
return Result.isBool(feedbackService.batchStatus(feedbackBatchStatusRequest));
}
}

View File

@@ -3,6 +3,7 @@ package com.corewing.app.service;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.dto.biz.feedback.FeedbackBatchStatusRequest;
import com.corewing.app.entity.Feedback; import com.corewing.app.entity.Feedback;
import java.util.List; import java.util.List;
@@ -12,6 +13,9 @@ import java.util.List;
*/ */
public interface FeedbackService extends IService<Feedback> { public interface FeedbackService extends IService<Feedback> {
Page<Feedback> page(Feedback feedback);
/** /**
* 创建反馈 * 创建反馈
* *
@@ -47,4 +51,11 @@ public interface FeedbackService extends IService<Feedback> {
* @return 是否成功 * @return 是否成功
*/ */
boolean updateStatus(Long id, Integer status); boolean updateStatus(Long id, Integer status);
/**
* 批量修改状态
* @param feedbackBatchStatusRequest
* @return
*/
boolean batchStatus(FeedbackBatchStatusRequest feedbackBatchStatusRequest);
} }

View File

@@ -1,24 +1,44 @@
package com.corewing.app.service.impl; package com.corewing.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.common.page.PageContext;
import com.corewing.app.dto.biz.feedback.FeedbackBatchStatusRequest;
import com.corewing.app.entity.Feedback; import com.corewing.app.entity.Feedback;
import com.corewing.app.mapper.FeedbackMapper; import com.corewing.app.mapper.FeedbackMapper;
import com.corewing.app.service.FeedbackService; import com.corewing.app.service.FeedbackService;
import com.corewing.app.util.I18nUtil; import com.corewing.app.util.I18nUtil;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.List; import java.util.List;
import static io.lettuce.core.GeoArgs.Unit.m;
/** /**
* 问题反馈 Service 实现类 * 问题反馈 Service 实现类
*/ */
@Service @Service
public class FeedbackServiceImpl extends ServiceImpl<FeedbackMapper, Feedback> implements FeedbackService { public class FeedbackServiceImpl extends ServiceImpl<FeedbackMapper, Feedback> implements FeedbackService {
@Resource
private FeedbackMapper feedbackMapper;
@Override
public Page<Feedback> page(Feedback feedback) {
Page<Feedback> page = PageContext.getPage(Feedback.class);
// LambdaQueryWrapper<Feedback> wrapper = new LambdaQueryWrapper<>();
// wrapper.eq(feedback.getStatus() != null, Feedback::getStatus, feedback.getStatus());
// wrapper.like(StringUtils.hasText(feedback.getTitle()), Feedback::getTitle, feedback.getTitle());
// wrapper.eq(StringUtils.hasText(feedback.getFeedbackType()), Feedback::getFeedbackType, feedback.getFeedbackType());
return feedbackMapper.page(page, feedback);
}
@Override @Override
public boolean createFeedback(Feedback feedback) { public boolean createFeedback(Feedback feedback) {
// 设置默认状态为待处理 // 设置默认状态为待处理
@@ -75,4 +95,16 @@ public class FeedbackServiceImpl extends ServiceImpl<FeedbackMapper, Feedback> i
return this.updateById(feedback); return this.updateById(feedback);
} }
@Transactional(rollbackFor = Exception.class)
@Override
public boolean batchStatus(FeedbackBatchStatusRequest feedbackBatchStatusRequest) {
feedbackBatchStatusRequest.getIds().forEach(id -> {
LambdaUpdateWrapper<Feedback> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(Feedback::getId, id);
wrapper.set(Feedback::getStatus, feedbackBatchStatusRequest.getStatus());
update(wrapper);
});
return true;
}
} }

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.corewing.app.mapper.FeedbackMapper">
<!-- 结果映射 -->
<resultMap id="VOResultMap" type="com.corewing.app.entity.Feedback">
<id column="id" property="id"/>
<result column="feedback_type" property="feedbackType"/>
<result column="title" property="title"/>
<result column="content" property="content"/>
<result column="contact" property="contact"/>
<result column="status" property="status"/>
<result column="nick_name" property="nickName"/>
<result column="username" property="username"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<!-- 基础查询SQL片段 -->
<sql id="selectVOSql">
select f.*,u.nick_name, u.username
from app_feedback f
left join app_user u on f.user_id = u.id
</sql>
<!-- 分页查询 -->
<select id="page" resultMap="VOResultMap">
<include refid="selectVOSql"/>
<where>
<if test="feedback.status != null">
AND f.status = #{feedback.status}
</if>
<if test="feedback.title != null and feedback.title != ''">
AND f.title like CONCAT('%', #{feedback.title}, '%')
</if>
<if test="feedback.feedbackType != null and feedback.feedbackType != ''">
AND f.feedbackType like CONCAT('%', #{feedback.feedbackType}, '%')
</if>
</where>
</select>
</mapper>

View File

@@ -42,8 +42,10 @@
@change="fetchData()" @change="fetchData()"
> >
<option value="">全部</option> <option value="">全部</option>
<option value="1">显示</option> <option value="0">待处理</option>
<option value="2">隐藏</option> <option value="1">处理中</option>
<option value="2">已完成</option>
<option value="3">已关闭</option>
</select> </select>
</div> </div>
</div> </div>
@@ -77,8 +79,11 @@
@change="toggleSelectAll()" @change="toggleSelectAll()"
> >
</th> </th>
<th>名称</th> <th>反馈问题</th>
<th>是否显示</th> <th>反馈详情</th>
<th>反馈用户</th>
<th>联系方式</th>
<th>流程</th>
<th>创建时间</th> <th>创建时间</th>
<th style="width: 120px;">操作</th> <th style="width: 120px;">操作</th>
</tr> </tr>
@@ -120,9 +125,12 @@
> >
</td> </td>
<td>{{ item.title }}</td> <td>{{ item.title }}</td>
<td class="ellipsis-single">{{ item.content }}</td>
<td>{{ item.nickName }}</td>
<td>{{ item.contact }}</td>
<td> <td>
<span class="badge" :class="item.status === 0 ? 'bg-warning' : 'bg-success'"> <span class="badge" :class="item.status === 0 ? 'bg-warning' : (item.status === 2 ? 'bg-success': (item.status === 3 ? 'bg-dark':'bg-info'))">
{{ item.status === 0 ? '待处理' : '已处理' }} {{ item.statusName }}
</span> </span>
</td> </td>
<td>{{ formatTime(item.createTime) }}</td> <td>{{ formatTime(item.createTime) }}</td>
@@ -208,35 +216,44 @@
<div class="modal-body"> <div class="modal-body">
<form> <form>
<div class="mb-3 row"> <div class="mb-3 row">
<label class="col-sm-3 col-form-label">名称</label> <label class="col-sm-3 col-form-label">反馈标题</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input type="text" class="form-control" v-model="addOrEditDto.title" <input type="text" class="form-control" v-model="addOrEditDto.title"
placeholder="请输入名称"> placeholder="请输入名称">
</div> </div>
</div> </div>
<div class="mb-3 row"> <div class="mb-3 row">
<label class="col-sm-3 col-form-label">内容</label> <label class="col-sm-3 col-form-label">联系方式</label>
<div class="col-sm-9"> <div class="col-sm-9">
<!-- 富文本编辑器容器 --> <input type="text" class="form-control" v-model="addOrEditDto.contact"
<div id="contentEditor" style="border: 1px solid #dee2e6; border-radius: 0.375rem;"></div> placeholder="请输入联系方式">
<!-- 错误提示 --> </div>
<div class="invalid-feedback" id="contentError" style="display: none; margin-top: 0.25rem;"></div> </div>
<div class="mb-3 row">
<label class="col-sm-3 col-form-label">反馈内容</label>
<div class="col-sm-9">
<!-- &lt;!&ndash; 富文本编辑器容器 &ndash;&gt;-->
<!-- <div id="contentEditor" style="border: 1px solid #dee2e6; border-radius: 0.375rem;"></div>-->
<!-- &lt;!&ndash; 错误提示 &ndash;&gt;-->
<!-- <div class="invalid-feedback" id="contentError" style="display: none; margin-top: 0.25rem;"></div>-->
<textarea class="form-control" v-model="addOrEditDto.content"></textarea>
</div> </div>
</div> </div>
<div class="mb-3 row"> <div class="mb-3 row">
<label class="col-sm-3 col-form-label">流程</label> <label class="col-sm-3 col-form-label">流程</label>
<div class="col-sm-9"> <div class="col-sm-9">
<select class="form-control" v-model="addOrEditDto.status"> <select class="form-control" v-model="addOrEditDto.status">
<option value=1>显示</option> <option value="0">待处理</option>
<option value=2>隐藏</option> <option value="1">处理中</option>
<option value="2">已完成</option>
<option value="3">已关闭</option>
</select> </select>
</div> </div>
</div> </div>
<div class="mb-3 row"> <div class="mb-3 row">
<label class="col-sm-3 col-form-label">序号</label> <label class="col-sm-3 col-form-label">反馈用户</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input type="number" class="form-control" v-model="addOrEditDto.sort" <input type="text" class="form-control" readonly v-model="addOrEditDto.nickName">
placeholder="请输入序号">
</div> </div>
</div> </div>
</form> </form>
@@ -283,19 +300,14 @@
batchAction: '', // 批量操作选择 batchAction: '', // 批量操作选择
pageRange: 5, pageRange: 5,
modalInstances: {}, modalInstances: {},
resetPasswordDto: {
userId: null,
nickName: null,
username: null,
password: null,
},
addOrEditTitle: '', addOrEditTitle: '',
addOrEditDto: { addOrEditDto: {
id: null, id: null,
title: null, title: null,
content: null, content: null,
visible: 1, contact: null,
sort: 99 status: 0,
nickName: 0,
}, },
editor: null, // WangEditor 实例 editor: null, // WangEditor 实例
} }
@@ -430,7 +442,7 @@
content: `确定要删除选中的 ${this.selectedIds.length} 条数据吗?`, content: `确定要删除选中的 ${this.selectedIds.length} 条数据吗?`,
onConfirm: async () => { onConfirm: async () => {
try { try {
const response = await request.post('/biz/privacy_policy/batchDelete', {ids: this.selectedIds}); const response = await request.post('/biz/feedback/batchDelete', {ids: this.selectedIds});
if (response.code === 200) { if (response.code === 200) {
$message.success("删除成功"); $message.success("删除成功");
this.fetchData(); this.fetchData();
@@ -450,7 +462,7 @@
content: '确定要删除这条数据吗?', content: '确定要删除这条数据吗?',
onConfirm: async () => { onConfirm: async () => {
try { try {
const response = await request.delete('/biz/privacy_policy/delete', {id}); const response = await request.delete('/biz/feedback/delete', {id});
if (response.code === 200) { if (response.code === 200) {
$message.success("删除成功"); $message.success("删除成功");
this.fetchData(); // 重新加载数据 this.fetchData(); // 重新加载数据
@@ -480,17 +492,18 @@
this.modalInstances['addOrEditModel'].show(); this.modalInstances['addOrEditModel'].show();
}, },
saveEntity() { saveEntity() {
let url = (this.addOrEditDto === null || this.addOrEditDto.id === null) ? '/biz/privacy_policy/save' : '/biz/privacy_policy/update'; let url = (this.addOrEditDto === null || this.addOrEditDto.id === null) ? '/biz/feedback/save' : '/biz/feedback/update';
request.post(url, this.addOrEditDto) request.post(url, this.addOrEditDto)
.then((res) => { .then((res) => {
if (res.code === 200) { if (res.code === 200) {
$message.success('保存成功!', 500); $message.success('保存成功!', 500);
this.fetchData(); this.fetchData();
this.clearForm();
this.modalInstances['addOrEditModel'].hide(); this.modalInstances['addOrEditModel'].hide();
} else { } else {
$message.error('保存失败!', 500); $message.error('保存失败!', 500);
} }
this.clearForm();
}).catch(() => { }).catch(() => {
@@ -502,8 +515,9 @@
id: null, id: null,
title: null, title: null,
content: null, content: null,
visible: 1, contact: null,
sort: 99 status: 0,
nickName: 0,
}; };
}, },
// 初始化 WangEditor // 初始化 WangEditor
@@ -558,20 +572,20 @@
} }
}); });
// 初始化 WangEditor延迟初始化确保DOM加载完成 // // 初始化 WangEditor延迟初始化确保DOM加载完成
setTimeout(() => { // setTimeout(() => {
this.initWangEditor(); // this.initWangEditor();
}, 500); // }, 500);
//
// 监听模态框关闭事件,避免内存泄漏 // // 监听模态框关闭事件,避免内存泄漏
const modal = document.getElementById('addOrEditModel'); // const modal = document.getElementById('addOrEditModel');
modal.addEventListener('hidden.bs.modal', () => { // modal.addEventListener('hidden.bs.modal', () => {
if (this.editor) { // if (this.editor) {
this.editor.txt.clear(); // 清空内容 // this.editor.txt.clear(); // 清空内容
document.getElementById('contentEditor').style.borderColor = '#dee2e6'; // document.getElementById('contentEditor').style.borderColor = '#dee2e6';
document.getElementById('contentError').style.display = 'none'; // document.getElementById('contentError').style.display = 'none';
} // }
}); // });
} }
}).mount('#app'); }).mount('#app');
</script> </script>