体检管理添加体检反馈功能

This commit is contained in:
2026-04-16 14:24:23 +08:00
parent 3261301074
commit afd8136475
6 changed files with 612 additions and 2 deletions
@@ -24,6 +24,7 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.web.commons.base.Globals;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.dayofficework.healthCheckup.model.*;
import com.budwk.app.zhgh.dayofficework.healthCheckup.service.HealthCheckupFeedbackService;
import com.budwk.app.zhgh.dayofficework.healthCheckup.service.HealthCheckupProjectService;
import com.budwk.app.zhgh.dayofficework.healthCheckup.template.HealthCheckupImportTemp;
import io.swagger.annotations.Api;
@@ -79,12 +80,21 @@ public class HealthCheckupListController {
@Inject
private HealthCheckupProjectService healthCheckupProjectService;
@Inject
private HealthCheckupFeedbackService healthCheckupFeedbackService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/healthCheckup/listMange/index.html")
@SaCheckPermission("healthCheckup.list.mange")
public void index() {
}
@At("/feedback/query")
@Ok("beetl:/platform/zhgh/dayofficework/healthCheckup/feedbackQuery/index.html")
@SaCheckPermission("healthCheckup.list.mange.feedback")
public void feedbackQueryIndex() {
}
@At
@ApiOperation("分页查询")
@SaCheckPermission("healthCheckup.list.mange")
@@ -468,6 +478,86 @@ public class HealthCheckupListController {
);
}
@At
@ApiOperation("体检反馈列表")
@SaCheckPermission("healthCheckup.list.mange")
public Result feedbackList(String year, String projectId, String unionId) {
if (StrUtil.isBlank(unionId)) {
unionId = SecurityUtil.getUnionId();
}
if (StrUtil.hasBlank(year, projectId, unionId)) {
return Result.error("请选择年度、体检项目、分工会后再查看反馈");
}
return Result.success(healthCheckupFeedbackService.listFeedback(year, projectId, unionId));
}
@At
@ApiOperation("保存体检反馈")
@SLog(type = "healthCheckup", tag = "体检管理", msg = "新增体检反馈")
@SaCheckPermission("healthCheckup.list.mange")
@Aop(TransAop.READ_COMMITTED)
public Result saveFeedback(String year, String projectId, String unionId, String feedbackInfo) {
if (StrUtil.isBlank(unionId)) {
unionId = SecurityUtil.getUnionId();
}
NutMap result = healthCheckupFeedbackService.saveFeedback(year, projectId, unionId, feedbackInfo);
if (result.getInt("code") == 0) {
return Result.success(result.getString("msg"));
}
return Result.error(result.getString("msg"));
}
@At
@ApiOperation("删除体检反馈")
@SLog(type = "healthCheckup", tag = "体检管理", msg = "删除体检反馈")
@SaCheckPermission("healthCheckup.list.mange")
@Aop(TransAop.READ_COMMITTED)
public Result deleteFeedback(String id) {
NutMap result = healthCheckupFeedbackService.deleteFeedback(id);
if (result.getInt("code") == 0) {
return Result.success(result.getString("msg"));
}
return Result.error(result.getString("msg"));
}
@At
@ApiOperation("批量删除体检反馈")
@SLog(type = "healthCheckup", tag = "体检管理", msg = "批量删除体检反馈")
@SaCheckPermission("healthCheckup.list.mange")
@Aop(TransAop.READ_COMMITTED)
public Result deleteFeedbackBatch(String ids) {
String[] idArray = null;
if (StrUtil.isNotBlank(ids)) {
// 兼容前端传逗号分隔字符串和JSON数组字符串两种格式。
if (ids.trim().startsWith("[")) {
idArray = JSONUtil.parseArray(ids).toList(String.class).toArray(new String[0]);
} else {
idArray = ids.split(",");
}
}
NutMap result = healthCheckupFeedbackService.deleteFeedbackBatch(idArray);
if (result.getInt("code") == 0) {
return Result.success(result.getString("msg"));
}
return Result.error(result.getString("msg"));
}
@At("/feedback/query/pageData")
@ApiOperation("问题反馈列表分页")
@SaCheckPermission("healthCheckup.list.mange")
public Result feedbackQueryPageData(PageForm pageForm, String year, String projectId, String unionId) {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name()) && StrUtil.isBlank(unionId)) {
unionId = SecurityUtil.getUnionId();
}
return Result.success(healthCheckupFeedbackService.listFeedbackPage(
pageForm.getPageNumber(),
pageForm.getPageSize(),
year,
projectId,
unionId
));
}
@At
@ApiOperation("体检名单编辑备注")
@SLog(type = "healthCheckup", tag = "体检管理", msg = "编辑备注")
@@ -0,0 +1,52 @@
package com.budwk.app.zhgh.dayofficework.healthCheckup.model;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* Health checkup feedback record.
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("health_checkup_feedback")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("health checkup feedback")
public class HealthCheckupFeedback extends BaseModel implements Serializable {
@Name
@Comment("id")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("year")
@ColDefine(type = ColType.VARCHAR, width = 4)
private String year;
@Column
@Comment("project id")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String projectId;
@Column
@Comment("project name")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String projectName;
@Column
@Comment("union id")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unionId;
@Column
@Comment("union name")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String unionName;
@Column
@Comment("feedback info")
@ColDefine(type = ColType.TEXT)
private String feedbackInfo;
}
@@ -0,0 +1,27 @@
package com.budwk.app.zhgh.dayofficework.healthCheckup.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.healthCheckup.model.HealthCheckupFeedback;
import org.nutz.lang.util.NutMap;
import java.util.List;
public interface HealthCheckupFeedbackService extends BaseService<HealthCheckupFeedback> {
/**
* Query feedback records by the selected filters on the list page.
*/
List<NutMap> listFeedback(String year, String projectId, String unionId);
/**
* Save a feedback record and bind it to the selected year, project and union.
*/
NutMap saveFeedback(String year, String projectId, String unionId, String feedbackInfo);
/**
* 删除单条体检反馈。
*/
NutMap deleteFeedback(String id);
NutMap deleteFeedbackBatch(String[] ids);
/**
* 查询问题反馈列表页数据。
*/
Pagination listFeedbackPage(Integer pageNumber, Integer pageSize, String year, String projectId, String unionId);
}
@@ -0,0 +1,126 @@
package com.budwk.app.zhgh.dayofficework.healthCheckup.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.zhgh.dayofficework.healthCheckup.model.HealthCheckupFeedback;
import com.budwk.app.zhgh.dayofficework.healthCheckup.model.HealthCheckupProject;
import com.budwk.app.zhgh.dayofficework.healthCheckup.service.HealthCheckupFeedbackService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import java.util.ArrayList;
import java.util.List;
@IocBean(args = {"refer:dao"})
public class HealthCheckupFeedbackServiceImpl extends BaseServiceImpl<HealthCheckupFeedback> implements HealthCheckupFeedbackService {
public HealthCheckupFeedbackServiceImpl(Dao dao) {
super(dao);
}
@Override
public List<NutMap> listFeedback(String year, String projectId, String unionId) {
Cnd cnd = Cnd.NEW();
cnd.and("year", "=", year)
.and("projectId", "=", projectId)
.and("unionId", "=", unionId)
.desc("createdAt");
List<HealthCheckupFeedback> feedbackList = query(cnd);
List<NutMap> result = new ArrayList<>();
for (HealthCheckupFeedback feedback : feedbackList) {
result.add(NutMap.NEW()
.addv("id", feedback.getId())
.addv("year", feedback.getYear())
.addv("projectName", feedback.getProjectName())
.addv("unionName", feedback.getUnionName())
.addv("feedbackInfo", feedback.getFeedbackInfo())
.addv("createdBy", feedback.getCreatedBy())
.addv("createdAt", feedback.getCreatedAt()));
}
return result;
}
@Override
public NutMap saveFeedback(String year, String projectId, String unionId, String feedbackInfo) {
if (StrUtil.isBlank(year) || StrUtil.isBlank(projectId) || StrUtil.isBlank(unionId)) {
return NutMap.NEW().addv("code", 1).addv("msg", "请选择年度、体检项目、分工会后再录入反馈");
}
if (StrUtil.isBlank(feedbackInfo)) {
return NutMap.NEW().addv("code", 1).addv("msg", "请输入反馈信息");
}
// 保存前先根据页面选中的条件反查项目和分工会,确保反馈和主列表条件强关联。
HealthCheckupProject project = dao().fetch(HealthCheckupProject.class, projectId);
if (project == null) {
return NutMap.NEW().addv("code", 1).addv("msg", "当前体检项目不存在,请重新选择");
}
Sys_union union = dao().fetch(Sys_union.class, unionId);
if (union == null) {
return NutMap.NEW().addv("code", 1).addv("msg", "当前分工会不存在,请重新选择");
}
HealthCheckupFeedback feedback = new HealthCheckupFeedback();
feedback.setYear(year);
feedback.setProjectId(projectId);
feedback.setProjectName(project.getName());
feedback.setUnionId(unionId);
feedback.setUnionName(union.getName());
feedback.setFeedbackInfo(feedbackInfo);
insert(feedback);
return NutMap.NEW().addv("code", 0).addv("msg", "保存成功");
}
@Override
public NutMap deleteFeedback(String id) {
if (StrUtil.isBlank(id)) {
return NutMap.NEW().addv("code", 1).addv("msg", "反馈ID不能为空");
}
HealthCheckupFeedback feedback = fetch(id);
if (feedback == null) {
return NutMap.NEW().addv("code", 1).addv("msg", "反馈记录不存在");
}
delete(id);
return NutMap.NEW().addv("code", 0).addv("msg", "删除成功");
}
@Override
public NutMap deleteFeedbackBatch(String[] ids) {
if (ids == null || ids.length == 0) {
return NutMap.NEW().addv("code", 1).addv("msg", "请选择需要删除的反馈");
}
Cnd cnd = Cnd.NEW();
cnd.and("id", "in", ids);
clear(cnd);
return NutMap.NEW().addv("code", 0).addv("msg", "批量删除成功");
}
@Override
public Pagination listFeedbackPage(Integer pageNumber, Integer pageSize, String year, String projectId, String unionId) {
Cnd cnd = Cnd.NEW();
cnd.andEX("hcf.year", "=", year);
cnd.andEX("hcf.projectId", "=", projectId);
cnd.andEX("hcf.unionId", "=", unionId);
cnd.desc("hcf.createdAt");
Sql sql = Sqls.create("""
SELECT
hcf.id,
hcf.year,
hcf.projectName,
hcf.unionName,
hcf.feedbackInfo,
hcf.createdAt,
su.username AS createdByName
FROM health_checkup_feedback hcf
LEFT JOIN sys_user su ON su.id = hcf.createdBy
$condition
""");
sql.setCondition(cnd);
return listPageMap(pageNumber, pageSize, sql);
}
}
@@ -0,0 +1,213 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" class="platform" v-cloak>
<guava ref="guava" padding="0 5%">
<template>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
style="width: 100%;"
value-format="yyyy"
placeholder="请选择年度"
@change="yearChange">
</el-date-picker>
</search-item>
<search-item label="体检项目">
<el-select v-model="pageForm.projectId"
style="width: 100%;"
clearable
@change="doSearch">
<el-option v-for="item in projectOptions"
:key="item.id"
:label="item.name"
:value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="分工会" v-if="$auth.hasRoleOr(['SYSADMIN', 'SCHOOL_UNION_ADMIN'])">
<el-select v-model="pageForm.unionId"
style="width: 100%;"
clearable
filterable
@change="doSearch">
<el-option v-for="item in unions"
:key="item.id"
:label="item.name"
:value="item.id"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never">
<table-tool label="问题反馈列表">
<el-button type="danger"
size="small"
:disabled="selectedFeedbackIds.length === 0"
@click="deleteFeedbackBatch">批量删除</el-button>
</table-tool>
<el-table :data="tableData"
style="width: 100%"
row-key="id"
:size="tableSize"
v-loading="tableLoading"
@selection-change="handleSelectionChange"
ref="table"
class="vi-table">
<el-table-column type="selection"
width="55"
align="center"
header-align="center"></el-table-column>
<el-table-column type="index"
:index="indexMethod"
label="序号"
width="80px"
align="center"
header-align="center"></el-table-column>
<el-table-column prop="year"
label="年度"
min-width="120px"
align="center"
header-align="center"
show-overflow-tooltip></el-table-column>
<el-table-column prop="projectName"
label="体检项目"
min-width="160px"
align="center"
header-align="center"
show-overflow-tooltip></el-table-column>
<el-table-column prop="unionName"
label="分工会"
min-width="160px"
align="center"
header-align="center"
show-overflow-tooltip></el-table-column>
<el-table-column prop="feedbackInfo"
label="反馈信息"
min-width="240px"
align="center"
header-align="center"
show-overflow-tooltip></el-table-column>
<el-table-column prop="createdByName"
label="创建人"
min-width="120px"
align="center"
header-align="center"
show-overflow-tooltip></el-table-column>
<el-table-column label="创建时间"
min-width="180px"
align="center"
header-align="center"
show-overflow-tooltip>
<template slot-scope="{row}">
{{ row.createdAt ? $moment(row.createdAt).format('YYYY-MM-DD HH:mm:ss') : '' }}
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</template>
</guava>
</div>
<script>
const vue = new Vue({
el: '#app',
mixins: [initTableMixins],
data() {
return {
tableData: [],
selectedFeedbackIds: [],
unions: [],
projectOptions: [],
}
},
methods: {
handleSelectionChange(rows) {
this.selectedFeedbackIds = rows.map(function (item) {
return item.id
})
},
async pageData() {
this.tableLoading = true
const resp = await this.$axios.post("/platform/healthCheckup/list/mange/feedback/query/pageData", this.pageForm)
if (resp.code === 0) {
this.tableData = [].concat(resp.data.list || [])
this.selectedFeedbackIds = []
this.pageForm.totalCount = resp.data.totalCount || 0
this.$nextTick(function () {
if (this.$refs.table) {
this.$refs.table.doLayout()
}
})
} else {
this.notifyError(resp.msg)
}
this.tableLoading = false
},
async doSearch() {
this.pageForm.pageNumber = 1
await this.pageData()
},
async deleteFeedbackBatch() {
if (this.selectedFeedbackIds.length === 0) {
this.notifyWarning("请选择需要删除的反馈")
return
}
const confirm = await this.$confirm("确认批量删除选中的反馈吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
if (confirm === "confirm") {
const resp = await this.$axios.post("/platform/healthCheckup/list/mange/deleteFeedbackBatch", {
ids: this.selectedFeedbackIds.join(",")
})
if (resp.code === 0) {
this.notifySuccess(resp.msg)
await this.pageData()
} else {
this.notifyError(resp.msg)
}
}
},
async yearChange() {
const resp = await $.get("/platform/healthCheckup/project/mange/getHealthCheckupProject", {year: this.pageForm.year})
if (resp.code === 0) {
this.projectOptions = resp.data
if (resp.data && resp.data.length > 0) {
const currentProjectId = this.pageForm.projectId
if (!currentProjectId || !resp.data.some(function (item) {
return item.id === currentProjectId
})) {
this.pageForm.projectId = resp.data[0].id
}
} else {
this.pageForm.projectId = ""
}
await this.doSearch()
} else {
this.notifyError(resp.msg)
}
}
},
async created() {
this.pageForm.year = this.$moment().format('YYYY')
this.unions = await this.$businessTool.listUnion()
if (!this.pageForm.unionId && this.unions && this.unions.length === 1) {
this.pageForm.unionId = this.unions[0].id
}
await this.yearChange()
}
})
</script>
<!--#
}
#-->
@@ -1,4 +1,4 @@
<!--#
<!--#
layout("/layouts/platform.html"){
#-->
@@ -85,6 +85,8 @@ layout("/layouts/platform.html"){
<el-radio-button :label="1">已选择</el-radio-button>
<el-radio-button :label="0">未选择</el-radio-button>
</el-radio-group>
<el-button type="primary" size="small" @click="openHealthFeedback"
v-if="$auth.hasRoleOr(['BRANCH_UNION_ADMIN'])">体检反馈</el-button>
<el-button type="primary" size="small" @click="openImport"
v-if="$auth.hasPermissionOr(['healthCheckup.list.import'])">导入名单
</el-button>
@@ -233,6 +235,27 @@ layout("/layouts/platform.html"){
</el-row>
</el-dialog>
<el-dialog title="体检反馈" :visible.sync="healthFeedbackDialog" width="60%"
:close-on-click-modal="false">
<el-form :model="healthFeedbackForm" label-width="100px">
<el-form-item label="反馈信息">
<el-input v-model="healthFeedbackForm.feedbackInfo" placeholder="请输入反馈信息"></el-input>
</el-form-item>
</el-form>
<el-row type="flex" justify="end" class="mb10">
<el-button @click="healthFeedbackDialog=false" type="primary" plain>取消</el-button>
<el-button @click="saveHealthFeedback" type="primary" v-loading="healthFeedbackLoading">保存</el-button>
</el-row>
<el-table :data="healthFeedbackList" border style="width: 100%">
<el-table-column align="center" label="问题反馈" prop="feedbackInfo" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="操作" width="100px">
<template slot-scope="{row}">
<el-button @click="deleteHealthFeedback(row)" type="danger" size="mini">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
<excel-import
ref="excelImportRef"
@@ -297,6 +320,15 @@ layout("/layouts/platform.html"){
projectId: "",
remarks: "",
},
healthFeedbackDialog: false,
healthFeedbackLoading: false,
healthFeedbackList: [],
healthFeedbackForm: {
year: "",
projectId: "",
unionId: "",
feedbackInfo: ""
},
campusOptions: [],
isEmployedOptions: [
{value: '是', label: '是'},
@@ -418,6 +450,72 @@ layout("/layouts/platform.html"){
this.notifyError(resp.msg)
}
},
validateHealthFeedbackCondition() {
// 反馈必须绑定当前筛选的年度和体检项目;分工会未选时由后端自动回落到当前登录人所属分工会。
if (!this.pageForm.year || !this.pageForm.projectId) {
this.notifyWarning("请先选择年度、体检项目后再打开体检反馈")
return false
}
return true
},
async openHealthFeedback() {
if (!this.validateHealthFeedbackCondition()) {
return
}
this.healthFeedbackForm.year = this.pageForm.year
this.healthFeedbackForm.projectId = this.pageForm.projectId
this.healthFeedbackForm.unionId = this.pageForm.unionId
this.healthFeedbackForm.feedbackInfo = ""
await this.queryHealthFeedbackList()
this.healthFeedbackDialog = true
},
async queryHealthFeedbackList() {
const resp = await $.get(loc() + "/feedbackList", {
year: this.pageForm.year,
projectId: this.pageForm.projectId,
unionId: this.pageForm.unionId
})
if (resp.code === 0) {
this.healthFeedbackList = resp.data || []
} else {
this.notifyError(resp.msg)
}
},
async saveHealthFeedback() {
if (!this.validateHealthFeedbackCondition()) {
return
}
if (!this.healthFeedbackForm.feedbackInfo) {
this.notifyWarning("请输入反馈信息")
return
}
this.healthFeedbackLoading = true
const resp = await $.post(loc() + "/saveFeedback", this.healthFeedbackForm)
if (resp.code === 0) {
this.notifySuccess(resp.msg)
this.healthFeedbackForm.feedbackInfo = ""
await this.queryHealthFeedbackList()
} else {
this.notifyError(resp.msg)
}
this.healthFeedbackLoading = false
},
async deleteHealthFeedback(row) {
const confirm = await this.$confirm("确认删除这条反馈吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
if (confirm === "confirm") {
const resp = await $.post(loc() + "/deleteFeedback", {id: row.id})
if (resp.code === 0) {
this.notifySuccess(resp.msg)
await this.queryHealthFeedbackList()
} else {
this.notifyError(resp.msg)
}
}
},
openImport() {
// importUser
@@ -438,7 +536,7 @@ layout("/layouts/platform.html"){
cancelButtonText: "取消",
type: "info"
}).then(async () => {
const resp = await this.$axios.post(loc() + '/doDelete', {id});
const resp = await $.post(loc() + '/doDelete', {id: id});
if (resp.code === 0) {
this.notifySuccess(resp.msg);
this.doSearch();
@@ -513,6 +611,9 @@ layout("/layouts/platform.html"){
},
async created() {
this.unions = await this.$businessTool.listUnion()
if (!this.pageForm.unionId && this.unions && this.unions.length === 1) {
this.pageForm.unionId = this.unions[0].id
}
this.flushUnits()
await this.yearChange()
this.pageData()
@@ -524,3 +625,4 @@ layout("/layouts/platform.html"){
<!--#
}
#-->