This commit is contained in:
2026-06-18 15:33:11 +08:00
parent 47e971ebc8
commit bedf31bf0c
4 changed files with 215 additions and 12 deletions
@@ -52,9 +52,9 @@ public class TourSchoolUserAssignmentController {
@At @At
@SaCheckPermission("tour.schoolUserAssignment") @SaCheckPermission("tour.schoolUserAssignment")
public Result pageData(PageForm pageForm, Integer year, String settingId, String matterId, String unionId, public Result pageData(PageForm pageForm, Integer year, String settingId, String matterId, String unionId,
String personType, String keyword, String assignSource) { String personType, String keyword, String assignSource, Boolean cancelled) {
return Result.success(tourUserAssignmentService.schoolAssignmentPage(pageForm, year, settingId, matterId, return Result.success(tourUserAssignmentService.schoolAssignmentPage(pageForm, year, settingId, matterId,
unionId, personType, keyword, assignSource)); unionId, personType, keyword, assignSource, cancelled));
} }
/** /**
@@ -64,9 +64,9 @@ public class TourSchoolUserAssignmentController {
@SaCheckPermission("tour.schoolUserAssignment") @SaCheckPermission("tour.schoolUserAssignment")
@Ok("void") @Ok("void")
public void exportData(PageForm pageForm, Integer year, String settingId, String matterId, String unionId, public void exportData(PageForm pageForm, Integer year, String settingId, String matterId, String unionId,
String personType, String keyword, String assignSource, HttpServletResponse response) { String personType, String keyword, String assignSource, Boolean cancelled, HttpServletResponse response) {
List<NutMap> rows = tourUserAssignmentService.schoolAssignmentExportRows(pageForm, year, settingId, matterId, List<NutMap> rows = tourUserAssignmentService.schoolAssignmentExportRows(pageForm, year, settingId, matterId,
unionId, personType, keyword, assignSource); unionId, personType, keyword, assignSource, cancelled);
ExportParams exportParams = new ExportParams(); ExportParams exportParams = new ExportParams();
exportParams.setSheetName("校工会人员分配"); exportParams.setSheetName("校工会人员分配");
exportParams.setType(ExcelType.XSSF); exportParams.setType(ExcelType.XSSF);
@@ -74,6 +74,27 @@ public class TourSchoolUserAssignmentController {
CommonDownloadUtil.download("校工会人员分配.xlsx", workbook, response); CommonDownloadUtil.download("校工会人员分配.xlsx", workbook, response);
} }
/**
* 按页面当前查询条件给人员分配列表中的可报名人员发送提醒。
* 参数包括列表筛选条件和 content 提醒内容;返回 Resultdata.receiverCount 表示实际接收人数。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.schoolUserAssignment")
@SLog(type = "tour", tag = "疗休养校工会人员分配", msg = "发送校工会人员分配提醒")
public Result sendReminder(PageForm pageForm, Integer year, String settingId, String matterId, String unionId,
String personType, String keyword, String assignSource, Boolean cancelled, String content) {
if (StrUtil.isBlank(content)) {
return Result.error("请输入提醒内容");
}
try {
return Result.success(tourUserAssignmentService.sendSchoolAssignmentReminder(pageForm, year, settingId,
matterId, unionId, personType, keyword, assignSource, cancelled, content));
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
/** /**
* 分页查询候选人员,候选范围由疗休养配置的可参加人员范围决定;userIds 仅作为人员选择器筛选条件。 * 分页查询候选人员,候选范围由疗休养配置的可参加人员范围决定;userIds 仅作为人员选择器筛选条件。
*/ */
@@ -21,10 +21,12 @@ public interface TourUserAssignmentService extends BaseService<TourUserAssignmen
* @param personType 人员类型:FORMAL 正式人员,BACKUP 替补人员 * @param personType 人员类型:FORMAL 正式人员,BACKUP 替补人员
* @param keyword 姓名或工号关键字 * @param keyword 姓名或工号关键字
* @param assignSource 分配类别:SCHOOL_UNION 校工会分配,BRANCH_UNION 分工会分配;为空时查询全部 * @param assignSource 分配类别:SCHOOL_UNION 校工会分配,BRANCH_UNION 分工会分配;为空时查询全部
* @param cancelled 是否退出:true 已退出,false 未退出,空时查询全部
* @return 校工会分配记录分页数据 * @return 校工会分配记录分页数据
*/ */
Pagination<NutMap> schoolAssignmentPage(PageForm pageForm, Integer year, String settingId, String matterId, Pagination<NutMap> schoolAssignmentPage(PageForm pageForm, Integer year, String settingId, String matterId,
String unionId, String personType, String keyword, String assignSource); String unionId, String personType, String keyword, String assignSource,
Boolean cancelled);
/** /**
* 按校工会人员分配页面当前筛选条件查询导出数据,不做分页截断。 * 按校工会人员分配页面当前筛选条件查询导出数据,不做分页截断。
@@ -37,10 +39,31 @@ public interface TourUserAssignmentService extends BaseService<TourUserAssignmen
* @param personType 人员类型:FORMAL 正式人员,BACKUP 替补人员 * @param personType 人员类型:FORMAL 正式人员,BACKUP 替补人员
* @param keyword 姓名或工号关键字 * @param keyword 姓名或工号关键字
* @param assignSource 分配类别:SCHOOL_UNION 校工会分配,BRANCH_UNION 分工会分配;为空时查询全部 * @param assignSource 分配类别:SCHOOL_UNION 校工会分配,BRANCH_UNION 分工会分配;为空时查询全部
* @param cancelled 是否退出:true 已退出,false 未退出,空时查询全部
* @return 符合当前页面查询条件的人员分配导出数据 * @return 符合当前页面查询条件的人员分配导出数据
*/ */
List<NutMap> schoolAssignmentExportRows(PageForm pageForm, Integer year, String settingId, String matterId, List<NutMap> schoolAssignmentExportRows(PageForm pageForm, Integer year, String settingId, String matterId,
String unionId, String personType, String keyword, String assignSource); String unionId, String personType, String keyword, String assignSource,
Boolean cancelled);
/**
* 按校工会人员分配页面当前筛选条件发送报名提醒。
*
* @param pageForm 排序参数,提醒接收人范围与当前列表筛选口径保持一致,不按当前分页截断
* @param year 疗休养年度
* @param settingId 疗休养配置ID
* @param matterId 疗休养事项ID
* @param unionId 所属分工会ID
* @param personType 人员类型:FORMAL 正式人员,BACKUP 替补人员
* @param keyword 姓名或工号关键字
* @param assignSource 分配类别:SCHOOL_UNION 校工会分配,BRANCH_UNION 分工会分配;为空时查询全部
* @param cancelled 是否退出:true 已退出,false 未退出,空时查询全部
* @param content 提醒内容,由页面弹框输入,不能为空
* @return 发送结果,receiverCount 表示本次去重后的接收人数
*/
NutMap sendSchoolAssignmentReminder(PageForm pageForm, Integer year, String settingId, String matterId,
String unionId, String personType, String keyword, String assignSource,
Boolean cancelled, String content);
/** /**
* 分页查询校工会可分配候选人,候选人来自疗休养配置的可参加人员范围,并排除同一配置下已分配人员。 * 分页查询校工会可分配候选人,候选人来自疗休养配置的可参加人员范围,并排除同一配置下已分配人员。
@@ -2,12 +2,15 @@ package com.budwk.app.zhgh.dayofficework.tour.service.impl;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.page.Pagination; import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm; import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_config; import com.budwk.app.sys.models.Sys_config;
import com.budwk.app.sys.services.SysConfigService; import com.budwk.app.sys.services.SysConfigService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.web.commons.base.Globals;
import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService;
import com.budwk.app.zhgh.dayofficework.tour.models.*; import com.budwk.app.zhgh.dayofficework.tour.models.*;
import com.budwk.app.zhgh.dayofficework.tour.service.TourUserAssignmentService; import com.budwk.app.zhgh.dayofficework.tour.service.TourUserAssignmentService;
import org.nutz.dao.Chain; import org.nutz.dao.Chain;
@@ -39,13 +42,17 @@ public class TourUserAssignmentServiceImpl extends BaseServiceImpl<TourUserAssig
@Inject @Inject
private SysConfigService sysConfigService; private SysConfigService sysConfigService;
@Inject
private GlobalMessageSendService globalMessageSendService;
public TourUserAssignmentServiceImpl(Dao dao) { public TourUserAssignmentServiceImpl(Dao dao) {
super(dao); super(dao);
} }
@Override @Override
public Pagination<NutMap> schoolAssignmentPage(PageForm pageForm, Integer year, String settingId, String matterId, public Pagination<NutMap> schoolAssignmentPage(PageForm pageForm, Integer year, String settingId, String matterId,
String unionId, String personType, String keyword, String assignSource) { String unionId, String personType, String keyword, String assignSource,
Boolean cancelled) {
Sql sql = Sqls.create(""" Sql sql = Sqls.create("""
SELECT SELECT
a.*, a.*,
@@ -70,6 +77,7 @@ public class TourUserAssignmentServiceImpl extends BaseServiceImpl<TourUserAssig
cnd.andEX("a.matterId", "=", matterId); cnd.andEX("a.matterId", "=", matterId);
cnd.andEX("a.unionId", "=", unionId); cnd.andEX("a.unionId", "=", unionId);
cnd.andEX("a.personType", "=", personType); cnd.andEX("a.personType", "=", personType);
appendCancelledCondition(cnd, cancelled);
if (StrUtil.isNotBlank(keyword)) { if (StrUtil.isNotBlank(keyword)) {
SqlExpressionGroup group = new SqlExpressionGroup(); SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("a.userName", keyword); group.orLike("a.userName", keyword);
@@ -83,12 +91,36 @@ public class TourUserAssignmentServiceImpl extends BaseServiceImpl<TourUserAssig
@Override @Override
public List<NutMap> schoolAssignmentExportRows(PageForm pageForm, Integer year, String settingId, String matterId, public List<NutMap> schoolAssignmentExportRows(PageForm pageForm, Integer year, String settingId, String matterId,
String unionId, String personType, String keyword, String assignSource) { String unionId, String personType, String keyword, String assignSource,
List<NutMap> rows = assignmentExportRows(pageForm, year, settingId, matterId, unionId, personType, keyword, assignSource); Boolean cancelled) {
List<NutMap> rows = assignmentExportRows(pageForm, year, settingId, matterId, unionId, personType, keyword, assignSource, cancelled);
fillAssignmentExportText(rows); fillAssignmentExportText(rows);
return rows; return rows;
} }
@Override
public NutMap sendSchoolAssignmentReminder(PageForm pageForm, Integer year, String settingId, String matterId,
String unionId, String personType, String keyword, String assignSource,
Boolean cancelled, String content) {
if (StrUtil.isBlank(content)) {
throw new IllegalArgumentException("请输入提醒内容");
}
List<NutMap> rows = assignmentExportRows(pageForm, year, settingId, matterId, unionId, personType, keyword, assignSource, cancelled);
List<String> receiverIds = rows.stream()
.map(row -> row.getString("userId"))
.filter(StrUtil::isNotBlank)
.distinct()
.collect(Collectors.toList());
if (Lang.isEmpty(receiverIds)) {
throw new IllegalArgumentException("当前查询条件下暂无可发送人员");
}
JSONObject config = new JSONObject();
config.set("pcUrl", Globals.AppDomain + "/platform/tour/schoolUserAssignment");
config.set("mobileUrl", Globals.AppDomain + "/platform/tooltip/h5");
globalMessageSendService.sendMessage("疗休养报名提醒", content.trim(), 1, receiverIds, config);
return NutMap.NEW().addv("receiverCount", receiverIds.size());
}
@Override @Override
public Pagination<NutMap> schoolCandidatePage(PageForm pageForm, String settingId, String unionId, String keyword, List<String> userIds) { public Pagination<NutMap> schoolCandidatePage(PageForm pageForm, String settingId, String unionId, String keyword, List<String> userIds) {
TourSetting setting = fetchSettingForAssignment(settingId); TourSetting setting = fetchSettingForAssignment(settingId);
@@ -128,7 +160,7 @@ public class TourUserAssignmentServiceImpl extends BaseServiceImpl<TourUserAssig
return Collections.emptyList(); return Collections.emptyList();
} }
List<NutMap> rows = assignmentExportRows(pageForm, year, settingId, matterId, unionId, personType, keyword, List<NutMap> rows = assignmentExportRows(pageForm, year, settingId, matterId, unionId, personType, keyword,
TourUserAssignment.ASSIGN_SOURCE_BRANCH_UNION); TourUserAssignment.ASSIGN_SOURCE_BRANCH_UNION, null);
fillAssignmentExportText(rows); fillAssignmentExportText(rows);
return rows; return rows;
} }
@@ -146,7 +178,8 @@ public class TourUserAssignmentServiceImpl extends BaseServiceImpl<TourUserAssig
* 构建人员分配导出查询,筛选条件与页面分页列表保持一致,但导出时不追加分页。 * 构建人员分配导出查询,筛选条件与页面分页列表保持一致,但导出时不追加分页。
*/ */
private List<NutMap> assignmentExportRows(PageForm pageForm, Integer year, String settingId, String matterId, private List<NutMap> assignmentExportRows(PageForm pageForm, Integer year, String settingId, String matterId,
String unionId, String personType, String keyword, String assignSource) { String unionId, String personType, String keyword, String assignSource,
Boolean cancelled) {
Sql sql = Sqls.create(""" Sql sql = Sqls.create("""
SELECT SELECT
a.*, a.*,
@@ -170,6 +203,7 @@ public class TourUserAssignmentServiceImpl extends BaseServiceImpl<TourUserAssig
cnd.andEX("a.matterId", "=", matterId); cnd.andEX("a.matterId", "=", matterId);
cnd.andEX("a.unionId", "=", unionId); cnd.andEX("a.unionId", "=", unionId);
cnd.andEX("a.personType", "=", personType); cnd.andEX("a.personType", "=", personType);
appendCancelledCondition(cnd, cancelled);
if (StrUtil.isNotBlank(keyword)) { if (StrUtil.isNotBlank(keyword)) {
SqlExpressionGroup group = new SqlExpressionGroup(); SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("a.userName", keyword); group.orLike("a.userName", keyword);
@@ -181,6 +215,20 @@ public class TourUserAssignmentServiceImpl extends BaseServiceImpl<TourUserAssig
return listMap(sql); return listMap(sql);
} }
/**
* 按是否退出筛选人员分配记录;未退出需要兼容历史空值,与页面展示逻辑保持一致。
*/
private void appendCancelledCondition(Cnd cnd, Boolean cancelled) {
if (cancelled == null) {
return;
}
if (Boolean.TRUE.equals(cancelled)) {
cnd.and("a.cancelled", "=", true);
return;
}
cnd.and(Cnd.exps("a.cancelled", "=", false).or("a.cancelled", "is", null));
}
/** /**
* 为 Excel 导出补充字典文本,避免前端标签渲染值直接出现在表格里。 * 为 Excel 导出补充字典文本,避免前端标签渲染值直接出现在表格里。
*/ */
@@ -42,6 +42,12 @@ layout("/layouts/platform.html"){
<el-option v-for="item in assignSourceOptions" :key="item.value" :label="item.label" :value="item.value"></el-option> <el-option v-for="item in assignSourceOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select> </el-select>
</search-item> </search-item>
<search-item label="是否退出">
<el-select v-model="pageForm.cancelled" clearable placeholder="请选择是否退出" style="width: 100%">
<el-option label="已退出" value="true"></el-option>
<el-option label="未退出" value="false"></el-option>
</el-select>
</search-item>
<search-item label="姓名/工号"> <search-item label="姓名/工号">
<el-input <el-input
v-model="pageForm.keyword" v-model="pageForm.keyword"
@@ -64,6 +70,7 @@ layout("/layouts/platform.html"){
<i class="el-icon-user"></i> <i class="el-icon-user"></i>
人员分配 人员分配
</el-button> </el-button>
<el-button type="warning" size="medium" icon="el-icon-message" @click="openRemindDialog">发送提醒</el-button>
<el-button type="primary" size="medium" icon="el-icon-download" @click="exportData">导出</el-button> <el-button type="primary" size="medium" icon="el-icon-download" @click="exportData">导出</el-button>
</table-tool> </table-tool>
<el-table <el-table
@@ -109,6 +116,30 @@ layout("/layouts/platform.html"){
</el-card> </el-card>
</guava> </guava>
<el-dialog
title="发送提醒"
:visible.sync="remindDialogVisible"
:close-on-click-modal="false"
width="560px"
@closed="resetRemindDialog">
<el-form :model="remindForm" :rules="remindRules" ref="remindFormRef" label-width="90px">
<el-form-item label="提醒内容" prop="content">
<el-input
v-model="remindForm.content"
type="textarea"
:rows="5"
maxlength="500"
show-word-limit
placeholder="请输入需要发送给当前查询结果人员的提醒内容">
</el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="remindDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="remindSubmitting" @click="doSendReminder">发送</el-button>
</span>
</el-dialog>
<el-dialog <el-dialog
custom-class="tour-user-assignment-dialog" custom-class="tour-user-assignment-dialog"
title="人员分配" title="人员分配"
@@ -310,6 +341,8 @@ layout("/layouts/platform.html"){
assignMatterOptions: [], assignMatterOptions: [],
unionOptions: [], unionOptions: [],
assignDialogVisible: false, assignDialogVisible: false,
remindDialogVisible: false,
remindSubmitting: false,
candidateLoading: false, candidateLoading: false,
candidateSelectLoading: false, candidateSelectLoading: false,
candidateData: [], candidateData: [],
@@ -332,6 +365,7 @@ layout("/layouts/platform.html"){
unionId: "", unionId: "",
personType: "", personType: "",
assignSource: "SCHOOL_UNION", assignSource: "SCHOOL_UNION",
cancelled: "false",
keyword: "" keyword: ""
}, },
assignForm: { assignForm: {
@@ -352,12 +386,18 @@ layout("/layouts/platform.html"){
matterId: "", matterId: "",
userName: "" userName: ""
}, },
remindForm: {
content: ""
},
assignRules: { assignRules: {
year: [{required: true, message: "请选择年度", trigger: ["change", "blur"]}], year: [{required: true, message: "请选择年度", trigger: ["change", "blur"]}],
settingId: [{required: true, message: "请选择疗休养配置", trigger: ["change", "blur"]}] settingId: [{required: true, message: "请选择疗休养配置", trigger: ["change", "blur"]}]
}, },
selectMatterRules: { selectMatterRules: {
matterId: [{required: true, message: "请选择分配线路", trigger: ["change", "blur"]}] matterId: [{required: true, message: "请选择分配线路", trigger: ["change", "blur"]}]
},
remindRules: {
content: [{required: true, message: "请输入提醒内容", trigger: ["blur"]}]
} }
} }
}, },
@@ -386,6 +426,11 @@ layout("/layouts/platform.html"){
userName: "" userName: ""
} }
}, },
defaultRemindForm() {
return {
content: ""
}
},
resetSearch() { resetSearch() {
const currentYear = moment().format("YYYY") const currentYear = moment().format("YYYY")
this.pageForm.year = currentYear this.pageForm.year = currentYear
@@ -395,6 +440,7 @@ layout("/layouts/platform.html"){
this.pageForm.personType = "" this.pageForm.personType = ""
// 页面打开和重置后默认查看校工会分配,清空该筛选项时后端会查询全部来源。 // 页面打开和重置后默认查看校工会分配,清空该筛选项时后端会查询全部来源。
this.pageForm.assignSource = "SCHOOL_UNION" this.pageForm.assignSource = "SCHOOL_UNION"
this.pageForm.cancelled = "false"
this.pageForm.keyword = "" this.pageForm.keyword = ""
this.loadSettingOptions() this.loadSettingOptions()
}, },
@@ -442,9 +488,24 @@ layout("/layouts/platform.html"){
} }
}) })
}, },
pageData() {
const params = Object.assign({}, this.pageForm)
if (params.cancelled === "") {
delete params.cancelled
}
this.tableLoading = true
this.$axios.post(loc() + "/pageData", params).then((res) => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
}
}).finally(() => {
this.tableLoading = false
})
},
exportData() { exportData() {
const params = new URLSearchParams() const params = new URLSearchParams()
;["year", "settingId", "matterId", "unionId", "personType", "assignSource", "keyword", "pageOrderName", "pageOrderBy"].forEach(key => { ;["year", "settingId", "matterId", "unionId", "personType", "assignSource", "cancelled", "keyword", "pageOrderName", "pageOrderBy"].forEach(key => {
const value = this.pageForm[key] const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") { if (value !== undefined && value !== null && value !== "") {
params.append(key, value) params.append(key, value)
@@ -452,6 +513,56 @@ layout("/layouts/platform.html"){
}) })
window.location.href = loc() + "/exportData?" + params.toString() window.location.href = loc() + "/exportData?" + params.toString()
}, },
buildCurrentQueryParams() {
const params = {}
;["year", "settingId", "matterId", "unionId", "personType", "assignSource", "cancelled", "keyword", "pageOrderName", "pageOrderBy"].forEach(key => {
const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") {
params[key] = value
}
})
return params
},
openRemindDialog() {
if (!this.pageForm.settingId) {
this.$message.warning("请先选择疗休养配置")
return
}
this.remindForm = this.defaultRemindForm()
this.remindDialogVisible = true
},
resetRemindDialog() {
this.remindForm = this.defaultRemindForm()
this.remindSubmitting = false
if (this.$refs.remindFormRef) {
this.$refs.remindFormRef.clearValidate()
}
},
doSendReminder() {
this.$refs.remindFormRef.validate((valid) => {
if (!valid) return
const params = this.buildCurrentQueryParams()
params.content = this.remindForm.content
this.$confirm("确定给当前查询结果中的人员发送提醒吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.remindSubmitting = true
this.$axios.post(loc() + "/sendReminder", params).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.$message.success("提醒发送成功,共发送" + (data.receiverCount || 0) + "人")
this.remindDialogVisible = false
} else {
this.$message.warning(res.msg || "提醒发送失败")
}
}).finally(() => {
this.remindSubmitting = false
})
}).catch(() => {})
})
},
openAssign() { openAssign() {
const year = this.pageForm.year || moment().format("YYYY") const year = this.pageForm.year || moment().format("YYYY")
this.assignForm = this.defaultAssignForm(year) this.assignForm = this.defaultAssignForm(year)