【新增】反馈管理

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)
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;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.entity.Feedback;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 问题反馈 Mapper 接口
@@ -10,4 +12,5 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
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.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.dto.biz.feedback.FeedbackBatchStatusRequest;
import com.corewing.app.entity.Feedback;
import java.util.List;
@@ -12,6 +13,9 @@ import java.util.List;
*/
public interface FeedbackService extends IService<Feedback> {
Page<Feedback> page(Feedback feedback);
/**
* 创建反馈
*
@@ -47,4 +51,11 @@ public interface FeedbackService extends IService<Feedback> {
* @return 是否成功
*/
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;
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.extension.plugins.pagination.Page;
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.mapper.FeedbackMapper;
import com.corewing.app.service.FeedbackService;
import com.corewing.app.util.I18nUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.List;
import static io.lettuce.core.GeoArgs.Unit.m;
/**
* 问题反馈 Service 实现类
*/
@Service
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
public boolean createFeedback(Feedback feedback) {
// 设置默认状态为待处理
@@ -75,4 +95,16 @@ public class FeedbackServiceImpl extends ServiceImpl<FeedbackMapper, Feedback> i
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()"
>
<option value="">全部</option>
<option value="1">显示</option>
<option value="2">隐藏</option>
<option value="0">待处理</option>
<option value="1">处理中</option>
<option value="2">已完成</option>
<option value="3">已关闭</option>
</select>
</div>
</div>
@@ -77,8 +79,11 @@
@change="toggleSelectAll()"
>
</th>
<th>名称</th>
<th>是否显示</th>
<th>反馈问题</th>
<th>反馈详情</th>
<th>反馈用户</th>
<th>联系方式</th>
<th>流程</th>
<th>创建时间</th>
<th style="width: 120px;">操作</th>
</tr>
@@ -120,9 +125,12 @@
>
</td>
<td>{{ item.title }}</td>
<td class="ellipsis-single">{{ item.content }}</td>
<td>{{ item.nickName }}</td>
<td>{{ item.contact }}</td>
<td>
<span class="badge" :class="item.status === 0 ? 'bg-warning' : 'bg-success'">
{{ item.status === 0 ? '待处理' : '已处理' }}
<span class="badge" :class="item.status === 0 ? 'bg-warning' : (item.status === 2 ? 'bg-success': (item.status === 3 ? 'bg-dark':'bg-info'))">
{{ item.statusName }}
</span>
</td>
<td>{{ formatTime(item.createTime) }}</td>
@@ -208,35 +216,44 @@
<div class="modal-body">
<form>
<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">
<input type="text" class="form-control" v-model="addOrEditDto.title"
placeholder="请输入名称">
</div>
</div>
<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 id="contentEditor" style="border: 1px solid #dee2e6; border-radius: 0.375rem;"></div>
<!-- 错误提示 -->
<div class="invalid-feedback" id="contentError" style="display: none; margin-top: 0.25rem;"></div>
<input type="text" class="form-control" v-model="addOrEditDto.contact"
placeholder="请输入联系方式">
</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 class="mb-3 row">
<label class="col-sm-3 col-form-label">流程</label>
<div class="col-sm-9">
<select class="form-control" v-model="addOrEditDto.status">
<option value=1>显示</option>
<option value=2>隐藏</option>
<option value="0">待处理</option>
<option value="1">处理中</option>
<option value="2">已完成</option>
<option value="3">已关闭</option>
</select>
</div>
</div>
<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">
<input type="number" class="form-control" v-model="addOrEditDto.sort"
placeholder="请输入序号">
<input type="text" class="form-control" readonly v-model="addOrEditDto.nickName">
</div>
</div>
</form>
@@ -283,19 +300,14 @@
batchAction: '', // 批量操作选择
pageRange: 5,
modalInstances: {},
resetPasswordDto: {
userId: null,
nickName: null,
username: null,
password: null,
},
addOrEditTitle: '',
addOrEditDto: {
id: null,
title: null,
content: null,
visible: 1,
sort: 99
contact: null,
status: 0,
nickName: 0,
},
editor: null, // WangEditor 实例
}
@@ -430,7 +442,7 @@
content: `确定要删除选中的 ${this.selectedIds.length} 条数据吗?`,
onConfirm: async () => {
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) {
$message.success("删除成功");
this.fetchData();
@@ -450,7 +462,7 @@
content: '确定要删除这条数据吗?',
onConfirm: async () => {
try {
const response = await request.delete('/biz/privacy_policy/delete', {id});
const response = await request.delete('/biz/feedback/delete', {id});
if (response.code === 200) {
$message.success("删除成功");
this.fetchData(); // 重新加载数据
@@ -480,17 +492,18 @@
this.modalInstances['addOrEditModel'].show();
},
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)
.then((res) => {
if (res.code === 200) {
$message.success('保存成功!', 500);
this.fetchData();
this.clearForm();
this.modalInstances['addOrEditModel'].hide();
} else {
$message.error('保存失败!', 500);
}
this.clearForm();
}).catch(() => {
@@ -502,8 +515,9 @@
id: null,
title: null,
content: null,
visible: 1,
sort: 99
contact: null,
status: 0,
nickName: 0,
};
},
// 初始化 WangEditor
@@ -558,20 +572,20 @@
}
});
// 初始化 WangEditor延迟初始化确保DOM加载完成
setTimeout(() => {
this.initWangEditor();
}, 500);
// 监听模态框关闭事件,避免内存泄漏
const modal = document.getElementById('addOrEditModel');
modal.addEventListener('hidden.bs.modal', () => {
if (this.editor) {
this.editor.txt.clear(); // 清空内容
document.getElementById('contentEditor').style.borderColor = '#dee2e6';
document.getElementById('contentError').style.display = 'none';
}
});
// // 初始化 WangEditor延迟初始化确保DOM加载完成
// setTimeout(() => {
// this.initWangEditor();
// }, 500);
//
// // 监听模态框关闭事件,避免内存泄漏
// const modal = document.getElementById('addOrEditModel');
// modal.addEventListener('hidden.bs.modal', () => {
// if (this.editor) {
// this.editor.txt.clear(); // 清空内容
// document.getElementById('contentEditor').style.borderColor = '#dee2e6';
// document.getElementById('contentError').style.display = 'none';
// }
// });
}
}).mount('#app');
</script>