feat: 普惠疗休养优化6

This commit is contained in:
2026-11-11 09:35:29 +08:00
parent 49237dc2ea
commit 336bea15bc
9 changed files with 169 additions and 38 deletions
@@ -18,6 +18,7 @@ import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.POST;
import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletRequest;
@@ -145,6 +146,26 @@ public class SysTaskController {
}
}
@At("/runOnce/?")
@POST
@Ok("json")
@SaCheckPermission("sys.manager.task.edit")
@SLog(tag = "执行任务一次", msg = "任务名:${args[1].getAttribute('name')}")
public Object runOnce(String id, HttpServletRequest req) {
Sys_task task = sysTaskService.fetch(id);
if (task == null) {
return Result.error("任务不存在");
}
req.setAttribute("name", task.getName());
try {
taskPlatformService.runOnce(task);
return Result.success("已提交执行");
} catch (Exception e) {
log.error("提交单次任务失败:" + task.getName(), e);
return Result.error("提交执行失败");
}
}
@At("/enable/?")
@Ok("json")
@SaCheckPermission("sys.manager.task.edit")
@@ -1,62 +1,50 @@
package com.budwk.app.task.job;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.param.ConditionGroup;
import com.budwk.app.sys.enums.SysDataUpdateMode;
import com.budwk.app.sys.models.Sys_user_source;
import com.budwk.app.sys.param.SysDataUserUpdateParam;
import com.budwk.app.sys.services.SysDataUserPullService;
import com.budwk.app.sys.services.SysDataUserUpdateService;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.util.NutMap;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Date;
/**
* 人员全量更新任务
*/
@IocBean
@Slf4j
@DisallowConcurrentExecution
public class SysUserAllRenewJob implements Job {
@Inject
private Dao dao;
@Inject("refer:$ioc")
private Ioc ioc;
@Inject
private SysDataUserPullService sysDataUserPullService;
@Inject
private SysDataUserUpdateService sysDataUserUpdateService;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Date pullTime = sysDataUserPullService.pull();
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
NutMap conditionGroupMap = (NutMap)jobDataMap.get("conditionGroup");
if(conditionGroupMap == null || conditionGroupMap.isEmpty()){
log.error("SysUserAllRenewJob定时任务执行中止,原因:conditionGroup为null");
}
String conditionGroupStr = Json.toJson(conditionGroupMap).replace("@currentDate", DateUtil.today());
ConditionGroup conditionGroup = null;
try {
conditionGroup = Json.fromJson(ConditionGroup.class, conditionGroupStr);
} catch (Exception e) {
log.error("SysUserAllRenewJob定时任务执行失败,原因:{}", e.getMessage());
SysDataUserUpdateParam param = new SysDataUserUpdateParam();
param.setUpdateMode(SysDataUpdateMode.ALL.name());
Object conditionGroup = jobExecutionContext.getMergedJobDataMap().get("conditionGroup");
if (conditionGroup != null) {
String conditionGroupJson = Json.toJson(conditionGroup).replace("@currentDate", DateUtil.today());
param.setConditionGroup(Json.fromJson(ConditionGroup.class, conditionGroupJson));
}
SysDataUserUpdateParam param = new SysDataUserUpdateParam();
param.setPullTime(DateUtil.formatDateTime(pullTime));
param.setUpdateMode(SysDataUpdateMode.ALL.name());
param.setConditionGroup(conditionGroup);
// sysDataUserUpdateService.update(param);
param.setPullTime(DateUtil.formatDateTime(sysDataUserPullService.pull()));
SysDataUserUpdateService updateService = (SysDataUserUpdateService) ioc.get(SysDataUpdateMode.ALL.getClazz());
log.info("人员全量更新任务,拉取批次 {}:{}", param.getPullTime(), updateService.update(param));
} catch (Exception e) {
throw new JobExecutionException("人员拉取及全量更新任务执行失败", e);
}
}
}
@@ -1,5 +1,7 @@
package com.budwk.app.task.services;
import com.budwk.app.sys.models.Sys_task;
import java.util.List;
/**
@@ -15,6 +17,8 @@ public interface TaskPlatformService {
*/
boolean isExist(String jobName, String jobGroup);
void runOnce(Sys_task task) throws Exception;
/**
* 添加新任务
*
@@ -1,12 +1,22 @@
package com.budwk.app.task.services.impl;
import com.budwk.app.sys.models.Sys_task;
import com.budwk.app.task.services.TaskPlatformService;
import org.nutz.integration.quartz.QuartzJob;
import org.nutz.integration.quartz.QuartzManager;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.quartz.CronExpression;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerUtils;
import org.quartz.impl.triggers.CronTriggerImpl;
@@ -23,6 +33,8 @@ import java.util.List;
public class TaskPlatformServiceImpl implements TaskPlatformService {
@Inject
private QuartzManager quartzManager;
@Inject
private Scheduler scheduler;
/**
* 判断任务是否存在
@@ -35,6 +47,28 @@ public class TaskPlatformServiceImpl implements TaskPlatformService {
return quartzManager.exist(new JobKey(jobName, jobGroup));
}
@Override
public void runOnce(Sys_task task) throws Exception {
JobKey key = JobKey.jobKey(task.getId(), task.getId());
if (scheduler.checkExists(key)) {
scheduler.triggerJob(key);
return;
}
Class<? extends Job> jobClass = Class.forName(task.getJobClass()).asSubclass(Job.class);
JobDataMap data = new JobDataMap();
if (Strings.isNotBlank(task.getData())) {
NutMap params = Json.fromJson(NutMap.class, task.getData());
if (params != null) {
data.putAll(params);
}
}
// 没有注册定时调度时,临时注册一个执行后自动移除的单次任务。
JobDetail job = JobBuilder.newJob(jobClass).withIdentity(key).usingJobData(data).build();
scheduler.scheduleJob(job, TriggerBuilder.newTrigger().forJob(key).startNow().build());
}
/**
* 添加新任务
*
@@ -12,6 +12,7 @@
#v4-home-app .portal-top { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 18px; max-width: 1720px; margin: auto; padding: 18px 24px 0; }
/* 使用完整不透明背景铺满横幅,会员信息独立成卡,不再覆盖校园景观。 */
#v4-home-app .portal-hero { position: relative; min-width: 0; min-height: 260px; overflow: hidden; border-radius: 12px; background: #e8f4ff url('/assets/platform/img/home/service-portal-banner-campus-v4.png') no-repeat right bottom / cover; }
#v4-home-app .portal-hero-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: right bottom; }
#v4-home-app .portal-hero::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(90deg, #e8f4ff 0%, #e8f4ff 28%, rgba(232,244,255,.92) 39%, rgba(232,244,255,.35) 54%, rgba(232,244,255,0) 68%); }
#v4-home-app .portal-hero-inner { position: relative; z-index: 1; display: flex; align-items: center; min-height: 260px; height: 100%; padding: 32px 40px; }
#v4-home-app .portal-hero-copy { max-width: 80%; }
@@ -118,12 +118,14 @@ layout("/layouts/v4/baseLayout.html"){
</style>
<link rel="stylesheet" href="${base!}/assets/platform/css/home-portal.css?v=20260929-1" />
<link rel="stylesheet" href="${base!}/assets/platform/css/home-portal.css?v=20261111-1" />
<div class="v4-container home-portal" id="v4-home-app">
<!-- 首页banner -->
<div class="portal-top">
<div class="portal-hero">
<img v-if="portalHeroImage" class="portal-hero-bg" :src="portalHeroImage" alt=""
@error="portalHeroImage = ''">
<div class="portal-hero-inner">
<div class="portal-hero-copy">
<div class="portal-hero-eyebrow">教职工一站式服务平台</div>
@@ -181,7 +183,7 @@ layout("/layouts/v4/baseLayout.html"){
data(){
return{
websiteNews: [],
homeBannerList: "${config.AppHomeImg!}".split(",").map(item => item.trim()).filter(item => item)
portalHeroImage: ""
}
},
components: {
@@ -193,9 +195,19 @@ layout("/layouts/v4/baseLayout.html"){
'stats': stats,
},
mounted() {
this.getPortalHeroImage()
this.getWebSiteNews()
},
methods: {
getPortalHeroImage() {
this.$axios.post("/open/common/getConfigKey", { key: "AppHomeImg" }).then(res => {
if (res.code === 0 && typeof res.data === "string") {
this.portalHeroImage = res.data.split(",").map(item => item.trim()).find(item => item) || ""
}
}).catch(() => {
this.portalHeroImage = ""
})
},
getWebSiteNews(){
this.$axios.post('/platform/home/getNews').then(res => {
if (res.code === 0) {
@@ -85,9 +85,24 @@ layout("/layouts/platform.html"){
<el-input maxlength="100" placeholder="参数名" v-model="formData.configKey" auto-complete="off"
tabindex="1" type="text"></el-input>
</el-form-item>
<el-form-item prop="configValue" label="参数值">
<el-input maxlength="100" placeholder="参数值" v-model="formData.configValue" auto-complete="off"
tabindex="2" type="text"></el-input>
<el-form-item v-if="addDialogVisible" prop="configValue" label="参数值">
<div>
<el-radio-group :value="configValueMode" @input="changeConfigValueMode">
<el-radio label="text">文本</el-radio>
<el-radio label="image">图片</el-radio>
</el-radio-group>
</div>
<file-upload
v-if="configValueMode === 'image'"
style="--upload-width: 214px;--upload-height:110px"
:value.sync="formData.configValue"
:upload_number="1"
upload_mode="image"
upload_result_category="interval"
upload_result_type="url"
></file-upload>
<el-input v-else maxlength="100" placeholder="参数值" v-model="formData.configValue"
auto-complete="off" tabindex="2" type="text"></el-input>
</el-form-item>
<el-form-item prop="note" label="说 明">
<el-input type="textarea" v-model="formData.note"></el-input>
@@ -111,7 +126,7 @@ layout("/layouts/platform.html"){
disabled
></el-input>
</el-form-item>
<el-form-item prop="configValue" label="参数值">
<el-form-item v-if="editDialogVisible" prop="configValue" label="参数值">
<template v-if="formData.configKey === 'AppLogo'">
<file-upload
key="AppLogo"
@@ -197,7 +212,22 @@ layout("/layouts/platform.html"){
<span>教代会代表证背景图片,通过文件上传配置</span>
</template>
<template v-else>
<div v-if="'true'===formData.configValue||'false'===formData.configValue">
<div>
<el-radio-group :value="configValueMode" @input="changeConfigValueMode">
<el-radio label="text">文本</el-radio>
<el-radio label="image">图片</el-radio>
</el-radio-group>
</div>
<file-upload
v-if="configValueMode === 'image'"
style="--upload-width: 214px;--upload-height:110px"
:value.sync="formData.configValue"
:upload_number="1"
upload_mode="image"
upload_result_category="interval"
upload_result_type="url"
></file-upload>
<div v-else-if="'true'===formData.configValue||'false'===formData.configValue">
<el-radio-group v-model="formData.configValue">
<el-radio label="true">是</el-radio>
<el-radio label="false">否</el-radio>
@@ -237,6 +267,8 @@ layout("/layouts/platform.html"){
tableResizeObserver: null,
pageRequestSequence: 0,
tableData: [],
configValueMode: "text",
alternateConfigValue: "",
pageForm: {
pageSize: 10,
pageNumber: 1,
@@ -263,7 +295,20 @@ layout("/layouts/platform.html"){
},
openAdd: function () {
this.addDialogVisible = true
this.formData = {} //打开新增窗口,表单先清空
this.formData = {configKey: "", configValue: "", note: ""} //打开新增窗口,表单先清空
this.configValueMode = "text"
this.alternateConfigValue = ""
},
changeConfigValueMode: function (mode) {
if (mode === this.configValueMode) {
return
}
var currentValue = this.formData.configValue
this.formData.configValue = this.alternateConfigValue
this.alternateConfigValue = currentValue
this.configValueMode = mode
var form = this.editDialogVisible ? this.$refs.editForm : this.$refs.addForm
form.clearValidate("configValue")
},
doAdd: function () {
var self = this
@@ -440,6 +485,9 @@ layout("/layouts/platform.html"){
function (data) {
if (data.code == 0) {
self.formData = data.data //加载后台表单数据
self.configValueMode = /^\/platform\/sys\/file\/download\?id=[a-z0-9]+$/i
.test(self.formData.configValue || "") ? "image" : "text"
self.alternateConfigValue = ""
self.editDialogVisible = true //打开编辑窗口
} else {
self.$message({
@@ -34,6 +34,9 @@ layout("/layouts/platform.html"){
<span class="ti-angle-down"></span>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="$auth.hasPermission('sys.manager.task.edit')"
:disabled="runningOnce[scope.row.id]"
:command="{type:'runOnce',id:scope.row.id}">执行一次</el-dropdown-item>
<el-dropdown-item :command="{type:'enable',id:scope.row.id,name:scope.row.name,row:scope.row}">启用</el-dropdown-item>
<el-dropdown-item :command="{type:'disable',id:scope.row.id,name:scope.row.name,row:scope.row}">
禁用
@@ -112,6 +115,7 @@ layout("/layouts/platform.html"){
addDialogVisible: false,
editDialogVisible: false,
tableData: [],
runningOnce: {},
pageForm: {
pageNumber: 1,
pageSize: 10,
@@ -264,6 +268,19 @@ layout("/layouts/platform.html"){
"json"
)
}
if ("runOnce" == command.type && !self.runningOnce[command.id]) {
self.$set(self.runningOnce, command.id, true)
$.post(base + "/platform/sys/task/runOnce/" + command.id, {}, function (data) {
self.$message({
message: data.msg,
type: data.code == 0 ? "success" : "error"
})
}, "json").fail(function () {
self.$message.error("提交执行失败")
}).always(function () {
self.$delete(self.runningOnce, command.id)
})
}
if ("edit" == command.type) {
$.post(
base + "/platform/sys/task/edit/" + command.id,
@@ -9,7 +9,8 @@ const home = {
<span class="home-summary-bar__title">智慧工会</span>
</div>
<div ref="homeHero" class="home-hero">
<img class="home-hero__bg" src="/assets/mobile/img/home/v4/hero-campus.png" alt="">
<img class="home-hero__bg" :src="homeHeroImage || '/assets/mobile/img/home/v4/hero-campus.png'" alt=""
@error="homeHeroImage = ''">
<div class="home-hero__color-layer" aria-hidden="true"></div>
<div class="home-hero__content">
<div class="home-hero__topbar">
@@ -287,6 +288,7 @@ const home = {
],
appLogo: "",
appName: "",
homeHeroImage: "",
activeBannerIndex: 0,
bannerLoadSeq: 0,
bannerImageCache: {},
@@ -610,6 +612,10 @@ const home = {
}
},
created() {
this.getConfigValue("H5AppHomeImg").then(value => {
const hero = this.parseBannerList(value)[0]
this.homeHeroImage = hero ? hero.src : ""
})
this.listHomeBrand()
this.listRecommendApp()
this.listClassroomCourses()