职工福利功能优化-1.1
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
package com.budwk.app.zhgh.welfare.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.zhgh.welfare.service.WelfareProjectService;
|
||||
@@ -25,11 +24,10 @@ public class WelfareNoticeController {
|
||||
@Inject
|
||||
private WelfareProjectService projectService;
|
||||
|
||||
@At("detail")
|
||||
@At
|
||||
@Ok("beetl:/platform/zhghh5/welfare/notice/detail.html")
|
||||
@SaCheckLogin
|
||||
@ApiOperation("福利通知附件详情页")
|
||||
public void detail() {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,8 +36,7 @@ public class WelfareNoticeController {
|
||||
* @param id 福利项目 ID
|
||||
* @return JSON 结果,data 中包含项目名称、文件名称、下载地址和 PDF 预览地址
|
||||
*/
|
||||
@At("detailData")
|
||||
@SaCheckLogin
|
||||
@At
|
||||
@ApiOperation("查询福利通知附件详情")
|
||||
public Result detailData(@Valid String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
|
||||
+3
-8
@@ -8,7 +8,6 @@ import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.welfare.model.WelfareList;
|
||||
import com.budwk.app.zhgh.welfare.model.WelfareProject;
|
||||
import com.budwk.app.zhgh.welfare.model.WelfareUserSelection;
|
||||
import com.budwk.app.zhgh.welfare.service.WelfareProjectService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -24,10 +23,8 @@ import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@@ -92,8 +89,6 @@ public class WelfareUserSelectController {
|
||||
if (count == 0) {
|
||||
return Result.error("您没有选择的权限");
|
||||
}
|
||||
WelfareProject project = dao.fetch(WelfareProject.class, projectId);
|
||||
|
||||
Date selectTime = new Date();
|
||||
String additionalInfoValidationMessage = welfareProjectService.validateSelectionAdditionalInfo(selections, selectTime);
|
||||
if (additionalInfoValidationMessage != null) {
|
||||
@@ -111,9 +106,9 @@ public class WelfareUserSelectController {
|
||||
return Result.error(groupQuantityValidationMessage);
|
||||
}
|
||||
|
||||
int sum = Arrays.stream(selections).mapToInt(selection -> Objects.requireNonNullElse(selection.getSelectNum(), 0)).sum();
|
||||
if (sum > project.getMultiSelectNum()) {
|
||||
return Result.error("选择的数量不能超过" + project.getMultiSelectNum());
|
||||
String projectQuantityValidationMessage = welfareProjectService.validateProjectSelectionQuantity(projectId, selections);
|
||||
if (projectQuantityValidationMessage != null) {
|
||||
return Result.error(projectQuantityValidationMessage);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -52,6 +52,15 @@ public interface WelfareProjectService extends BaseService<WelfareProject> {
|
||||
*/
|
||||
String validateGroupSelectionQuantity(String projectId, WelfareUserSelection[] selections);
|
||||
|
||||
/**
|
||||
* 校验福利项目一次提交的总选择份数。
|
||||
*
|
||||
* @param projectId 福利项目 ID,用于读取单选或多选模式及多选数量上限
|
||||
* @param selections 用户提交的福利选择数组,每项需包含 selectOptionId 和 selectNum
|
||||
* @return 校验通过返回 {@code null};超过项目上限或多选项目未配置上限时返回错误提示
|
||||
*/
|
||||
String validateProjectSelectionQuantity(String projectId, WelfareUserSelection[] selections);
|
||||
|
||||
/**
|
||||
* 校验福利选择附加信息。同一次提交的全部福利项必须使用相同备注,配送时间字段已停用。
|
||||
*
|
||||
|
||||
@@ -201,6 +201,40 @@ public class WelfareProjectServiceImpl extends BaseServiceImpl<WelfareProject> i
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验福利项目总选择份数。单选项目固定只能选择 1 份,不读取仅供多选项目使用的
|
||||
* multiSelectNum;多选项目必须配置有效的数量上限。
|
||||
*
|
||||
* @param projectId 福利项目 ID
|
||||
* @param selections 当前用户提交的福利选择数据
|
||||
* @return 校验通过返回 {@code null},否则返回可直接展示的错误提示
|
||||
*/
|
||||
@Override
|
||||
public String validateProjectSelectionQuantity(String projectId, WelfareUserSelection[] selections) {
|
||||
WelfareProject project = fetch(projectId);
|
||||
if (project == null) {
|
||||
return "福利项目不存在";
|
||||
}
|
||||
int selectedQuantity = Arrays.stream(selections == null ? new WelfareUserSelection[0] : selections)
|
||||
.filter(Objects::nonNull)
|
||||
.mapToInt(selection -> Objects.requireNonNullElse(selection.getSelectNum(), 0))
|
||||
.sum();
|
||||
boolean multipleSelection = "checkBox".equals(project.getIsCheckBox());
|
||||
int maxSelectQuantity;
|
||||
if (multipleSelection) {
|
||||
if (project.getMultiSelectNum() == null || project.getMultiSelectNum() <= 0) {
|
||||
return "福利项目未配置最多选择数量";
|
||||
}
|
||||
maxSelectQuantity = project.getMultiSelectNum();
|
||||
} else {
|
||||
maxSelectQuantity = 1;
|
||||
}
|
||||
if (selectedQuantity > maxSelectQuantity) {
|
||||
return "选择的数量不能超过" + maxSelectQuantity;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验一次选择提交中的备注。配送时间字段已停用,新提交数据统一清空该字段;
|
||||
* 多项福利的备注必须一致,避免同一次选择出现互相冲突的附加信息。
|
||||
|
||||
+3
-2
@@ -145,7 +145,7 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl<Welfar
|
||||
|
||||
String title = project.getName() + "-福利通知";
|
||||
String detailUrl = StrUtil.removeSuffix(Globals.AppDomain, "/")
|
||||
+ "/platform/welfare/notice/detail?id=" + project.getId();
|
||||
+ "/platform/welfare/notice/index?id=" + project.getId();
|
||||
String detailLine = "查看详情:" + detailUrl;
|
||||
// 移除前端预置的详情行后统一追加到正文末尾,既避免重复链接,也保证最终消息排版一致。
|
||||
String messageBody = Arrays.stream(plainContent.split("\\R"))
|
||||
@@ -153,7 +153,8 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl<Welfar
|
||||
.collect(Collectors.joining("\n"))
|
||||
.trim();
|
||||
String sendContent = StrUtil.isBlank(messageBody) ? detailLine : messageBody + "\n" + detailLine;
|
||||
boolean success = smsService.sendMsg("6", loginNames, null, title, sendContent, detailUrl, detailUrl);
|
||||
// 详情地址已经包含在消息正文中,跳转参数保持为空,避免消息中心再次追加 PC、移动端详情链接。
|
||||
boolean success = smsService.sendMsg("6", loginNames, null, title, sendContent, null, null);
|
||||
if (!success) {
|
||||
throw new BaseException("钉钉消息发送失败,请检查消息发送配置或稍后重试");
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ layout("/layouts/platform.html"){
|
||||
return ""
|
||||
}
|
||||
const appDomain = typeof APP_DOMAIN !== "undefined" && APP_DOMAIN ? APP_DOMAIN : window.location.origin
|
||||
return appDomain.replace(/\/+$/, "") + "/platform/welfare/notice/detail?id=" + encodeURIComponent(this.pageForm.projectId)
|
||||
return appDomain.replace(/\/+$/, "") + "/platform/welfare/notice/index?id=" + encodeURIComponent(this.pageForm.projectId)
|
||||
},
|
||||
welfareOptions() {
|
||||
if (this.pageForm.projectId) {
|
||||
|
||||
@@ -242,7 +242,8 @@ layout("/layouts/platform_h5.html"){
|
||||
.welfare-detail-link {
|
||||
display: inline-flex;
|
||||
height: 20px;
|
||||
margin-left: 6px;
|
||||
margin-left: auto;
|
||||
padding-left: 6px;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
color: #1989fa;
|
||||
@@ -827,8 +828,8 @@ layout("/layouts/platform_h5.html"){
|
||||
<span>{{ optionRankText(option) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="welfare-option-desc-row" v-if="optionBrief(option)">
|
||||
<div class="welfare-option-desc">
|
||||
<div class="welfare-option-desc-row">
|
||||
<div class="welfare-option-desc" v-if="optionBrief(option)">
|
||||
{{ optionBrief(option) }}
|
||||
</div>
|
||||
<div class="welfare-detail-link" @click.stop="showOptionDetail(option)">
|
||||
@@ -847,7 +848,8 @@ layout("/layouts/platform_h5.html"){
|
||||
integer
|
||||
disable-input
|
||||
:min="option.isSystemDefault ? 1 : 0"
|
||||
:max="projectInfo.isCheckBox === 'radio' ? 1 : selectionLimit"
|
||||
:max="getOptionMaxSelectNum(option)"
|
||||
:disable-plus="isOptionIncreaseDisabled(option)"
|
||||
:disabled="isDeadlinePassed || (projectInfo.isCheckBox === 'radio' && systemDefaultOption && systemDefaultOption.id !== option.id)"
|
||||
input-width="40px"
|
||||
button-size="22px"
|
||||
@@ -984,9 +986,11 @@ layout("/layouts/platform_h5.html"){
|
||||
<van-action-sheet v-model="showOptionDetailDialog" class="welfare-detail-sheet"
|
||||
:style="{ height: '78%' }" :close-on-click-overlay="true"
|
||||
:title="selectedOption ? selectedOption.optionName : ''" cancel-text="取消"
|
||||
@close="handleHistoryLayerComponentClose('detail')">
|
||||
@close="handleHistoryLayerComponentClose('detail')">
|
||||
<div class="welfare-detail-popup" v-if="selectedOption">
|
||||
<div class="welfare-detail-content" v-html="selectedOption.description"></div>
|
||||
<div class="welfare-detail-content" v-if="hasOptionDetail(selectedOption)"
|
||||
v-html="selectedOption.description"></div>
|
||||
<van-empty v-else description="暂无详情"></van-empty>
|
||||
</div>
|
||||
</van-action-sheet>
|
||||
|
||||
@@ -1196,6 +1200,20 @@ layout("/layouts/platform_h5.html"){
|
||||
const text = (div.textContent || div.innerText || "").replace(/\s+/g, " ").trim()
|
||||
return text
|
||||
},
|
||||
/**
|
||||
* 判断福利选项是否存在可展示的详情内容。
|
||||
* @param option 福利选项,详情来源为 description 字段。
|
||||
* @returns {boolean} 存在文字或富媒体内容时返回 true,否则详情弹框显示“暂无详情”。
|
||||
*/
|
||||
hasOptionDetail(option) {
|
||||
if (!option || !option.description) {
|
||||
return false
|
||||
}
|
||||
const div = document.createElement("div")
|
||||
div.innerHTML = option.description
|
||||
const text = (div.textContent || div.innerText || "").replace(/\s+/g, " ").trim()
|
||||
return !!text || !!div.querySelector("img,table,video,audio,iframe")
|
||||
},
|
||||
selectNumChange(index, newValue) {
|
||||
const option = this.projectInfo.options[index];
|
||||
if (option.isSystemDefault && Number(newValue) < 1) {
|
||||
@@ -1203,6 +1221,12 @@ layout("/layouts/platform_h5.html"){
|
||||
this.$toast.fail("系统默认福利不可取消")
|
||||
return
|
||||
}
|
||||
if (!this.projectInfo.singleOptionSupportMultipleSelection && Number(newValue) > 1) {
|
||||
this.$set(option, "selectNum", 1)
|
||||
this.$set(option, "selectNumKey", (option.selectNumKey || 0) + 1)
|
||||
this.$toast.fail("此套餐最多只能选择1份")
|
||||
return
|
||||
}
|
||||
const maxSelect = this.projectInfo.multiSelectNum || this.projectInfo.options.length;
|
||||
|
||||
// 计算其他所有选项的总和(不包括当前修改的选项)
|
||||
@@ -1223,7 +1247,7 @@ layout("/layouts/platform_h5.html"){
|
||||
|
||||
// 如果当前选项的值大于允许的最大值,则调整它
|
||||
if (option.selectNum > maxAllowedForThisOption) {
|
||||
option.selectNum = maxAllowedForThisOption;
|
||||
this.$set(option, "selectNum", maxAllowedForThisOption)
|
||||
}
|
||||
// 如果当前选项的值小于或等于允许的最大值(例如,用户减少了数量导致总数超标,但实际上减少后并未超过 maxSelect,
|
||||
// 这种情况理论上不应该在 change 时发生,因为 change 是值改变后触发的,且 change 后的值导致了超标),
|
||||
@@ -1247,6 +1271,44 @@ layout("/layouts/platform_h5.html"){
|
||||
return Number(option.selectNum) || 0
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算当前选项可达到的最大份数。
|
||||
* 多选项目同时受项目剩余份数和单个套餐是否允许多份控制;单选项目固定为1。
|
||||
* @param option 当前福利选项,selectNum为已选份数
|
||||
* @return {number} 传给步进器max属性的最大份数
|
||||
*/
|
||||
getOptionMaxSelectNum(option) {
|
||||
if (this.projectInfo.isCheckBox === "radio") {
|
||||
return 1
|
||||
}
|
||||
const currentQuantity = Number(option.selectNum) || 0
|
||||
const globalAllowedQuantity = currentQuantity + this.remainingSelectionCount
|
||||
if (!this.projectInfo.singleOptionSupportMultipleSelection) {
|
||||
return Math.min(1, globalAllowedQuantity)
|
||||
}
|
||||
return globalAllowedQuantity
|
||||
},
|
||||
|
||||
/**
|
||||
* 判断当前选项是否还能增加份数。
|
||||
* 达到项目总上限或单个套餐已达到1份且不允许多份时禁用加号,但不影响减号取消。
|
||||
* @param option 当前福利选项,selectNum为已选份数
|
||||
* @return {boolean} true表示禁用当前步进器的加号
|
||||
*/
|
||||
isOptionIncreaseDisabled(option) {
|
||||
if (this.isDeadlinePassed) {
|
||||
return true
|
||||
}
|
||||
if (this.projectInfo.isCheckBox === "radio") {
|
||||
return this.selectedRadioId === option.id
|
||||
}
|
||||
const currentQuantity = Number(option.selectNum) || 0
|
||||
if (!this.projectInfo.singleOptionSupportMultipleSelection && currentQuantity >= 1) {
|
||||
return true
|
||||
}
|
||||
return this.remainingSelectionCount <= 0
|
||||
},
|
||||
|
||||
/**
|
||||
* 统一处理单选和多选项目的数量变化,保持原有默认项及总数量限制。
|
||||
* @param index 福利选项索引
|
||||
@@ -1261,16 +1323,28 @@ layout("/layouts/platform_h5.html"){
|
||||
return
|
||||
}
|
||||
if (option.isSystemDefault) return
|
||||
this.selectedRadioId = ""
|
||||
this.projectInfo.options.forEach((item) => {
|
||||
item.selectNum = 0
|
||||
})
|
||||
this.syncRadioOptionSelection("")
|
||||
return
|
||||
}
|
||||
this.$set(option, "selectNum", quantity)
|
||||
this.selectNumChange(index, quantity)
|
||||
},
|
||||
|
||||
/**
|
||||
* 同步单选项目的选中项和全部步进器显示值。
|
||||
* @param optionId 当前选中的福利选项 ID;传空字符串表示取消当前选择
|
||||
* @return {void} 无返回值,未选项统一显示 0,选中项显示 1
|
||||
*/
|
||||
syncRadioOptionSelection(optionId) {
|
||||
this.$set(this, "selectedRadioId", optionId || "")
|
||||
if (!this.projectInfo.options) return
|
||||
this.projectInfo.options.forEach((option) => {
|
||||
this.$set(option, "selectNum", option.id === optionId ? 1 : 0)
|
||||
// Vant Stepper 会保留内部显示值,切换单选项时更新 key 强制同步所有控件。
|
||||
this.$set(option, "selectNumKey", (option.selectNumKey || 0) + 1)
|
||||
})
|
||||
},
|
||||
|
||||
// 保留原有首次选择方法,兼容其他调用入口。
|
||||
selectCheckboxOption(index) {
|
||||
if (this.isDeadlinePassed) {
|
||||
@@ -1366,11 +1440,7 @@ layout("/layouts/platform_h5.html"){
|
||||
if (this.projectInfo.options && this.userSelection.length > 0) {
|
||||
// 单选模式
|
||||
if (this.projectInfo.isCheckBox === "radio" && this.userSelection[0]) {
|
||||
this.selectedRadioId = this.userSelection[0].selectOptionId
|
||||
const selectedOption = this.projectInfo.options.find((opt) => opt.id === this.selectedRadioId)
|
||||
if (selectedOption) {
|
||||
selectedOption.selectNum = 1
|
||||
}
|
||||
this.syncRadioOptionSelection(this.userSelection[0].selectOptionId)
|
||||
}
|
||||
// 多选模式
|
||||
else {
|
||||
@@ -1620,22 +1690,11 @@ layout("/layouts/platform_h5.html"){
|
||||
return
|
||||
}
|
||||
if (this.systemDefaultOption && this.systemDefaultOption.id !== optionId) {
|
||||
this.selectedRadioId = this.systemDefaultOption.id
|
||||
this.syncRadioOptionSelection(this.systemDefaultOption.id)
|
||||
this.$toast.fail("系统默认福利不可取消")
|
||||
return
|
||||
}
|
||||
this.selectedRadioId = optionId
|
||||
|
||||
// 重置所有选项的selectNum
|
||||
if (this.projectInfo.options) {
|
||||
this.projectInfo.options.forEach((option) => {
|
||||
if (option.id === optionId) {
|
||||
option.selectNum = 1
|
||||
} else {
|
||||
option.selectNum = 0
|
||||
}
|
||||
})
|
||||
}
|
||||
this.syncRadioOptionSelection(optionId)
|
||||
},
|
||||
|
||||
// 默认项只在首次选择时自动带入,已有历史选择不自动覆盖。
|
||||
|
||||
Reference in New Issue
Block a user