1、生日祝福,采用杭医版

2、离退休做个链接图标
This commit is contained in:
2026-06-29 16:40:33 +08:00
parent a519e86c65
commit a1f19fda82
44 changed files with 5625 additions and 3 deletions
@@ -0,0 +1,85 @@
package com.budwk.app.task.job.staffmanage;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.sys.models.Sys_msg;
import com.budwk.app.sys.services.SysMsgService;
import com.budwk.app.web.commons.base.Globals;
import com.budwk.app.zhgh.staffmanage.birthday.model.UserBirthdayConfig;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayMsgLogService;
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.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Times;
import org.nutz.lang.util.NutMap;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.List;
/**
* 生日祝福定时任务,每天向当天生日的会员发送祝福消息。
*/
@IocBean
public class UserBirthdayJob implements Job {
@Inject
private Dao dao;
@Inject
private SysMsgService sysMsgService;
@Inject
private UserBirthdayMsgLogService userBirthdayMsgLogService;
/**
* 查询当天生日会员并发送生日贺卡链接。
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
Sql sql = Sqls.create("""
SELECT
id,
loginname,
username,
unitName,
unionName,
mobile
FROM
vw_user
WHERE
MONTH(birthday) = MONTH(CURDATE())
AND DAY(birthday) = DAY(CURDATE())
AND member = 1
AND birthday IS NOT NULL
""");
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
List<NutMap> users = sql.getList(NutMap.class);
if (CollUtil.isEmpty(users)) {
return;
}
UserBirthdayConfig config = dao.fetch(UserBirthdayConfig.class, Cnd.NEW().desc("updatedAt"));
String link = Globals.AppDomain + "/platform/staffManage/birthday/manage/h5?id=" + StrUtil.blankToDefault(config == null ? "" : config.getBirthdayUrl(), "");
List<String> loginNames = users.stream().map(user -> user.getString("loginname")).distinct().toList();
String title = "请点击查收您的生日祝福";
String content = "校工会祝您:生日快乐,幸福安康!";
Sys_msg sysMsg = new Sys_msg();
sysMsg.setTitle(title);
sysMsg.setNote(content);
sysMsg.setUrl(link);
sysMsg.setType("user");
sysMsg.setSendType("hide");
sysMsg.setWechatEnterprise(true);
sysMsg.setSendAt(Times.getTS());
sysMsg.setCreatedBy("system");
sysMsgService.saveMsg(sysMsg, loginNames.toArray(String[]::new), true);
userBirthdayMsgLogService.insertLogs(users, title, content, "", link);
}
}
@@ -0,0 +1,120 @@
package com.budwk.app.zhgh.staffmanage.birthday.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayService;
import com.budwk.app.zhgh.staffmanage.birthday.vo.BirthdayGreetingSendVO;
import com.budwk.app.zhgh.staffmanage.birthday.vo.UserBirthdayPageVO;
import io.swagger.annotations.Api;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
/**
* 移动端生日祝福墙控制器,提供最近生日人员、赠礼和贺卡详情能力。
*/
@IocBean
@At("/mobile/birthday/greeting")
@Ok("json:full")
@Api(tags = "移动端生日祝福墙")
public class BirthdayGreetingsController {
@Inject
private UserBirthdayService userBirthdayService;
/**
* 最近生日人员页面。
*/
@At("")
@Ok("beetl:/platform/zhghh5/birthday/greeting.html")
@SaCheckLogin
public void index() {
}
/**
* 我收到的祝福页面。
*/
@At("/greeting_info")
@Ok("beetl:/platform/zhghh5/birthday/greeting_info.html")
@SaCheckLogin
public void info() {
}
/**
* 赠送祝福页面。
*/
@At("/greeting_list")
@Ok("beetl:/platform/zhghh5/birthday/greeting_list.html")
@SaCheckLogin
public void list() {
}
/**
* 贺卡详情页面。
*/
@At("/hk")
@Ok("beetl:/platform/zhghh5/birthday/hk.html")
@SaCheckLogin
public void hk() {
}
/**
* 查询最近生日人员。
*/
@At
@SaCheckLogin
public Result pageData(UserBirthdayPageVO pageForm, String searchKeyWord) {
return Result.success(userBirthdayService.listGreetingUsers(pageForm, searchKeyWord));
}
/**
* 查询勾选的祝福对象。
*/
@At
@SaCheckLogin
public Result loadList(String greetingResult) {
return Result.success(userBirthdayService.loadGreetingUsers(greetingResult));
}
/**
* 查询祝福礼物列表。
*/
@At
@SaCheckLogin
public Result giftList() {
return Result.success(userBirthdayService.giftList());
}
/**
* 保存赠送祝福记录。
*/
@At
@SaCheckLogin
public Result sendBlessing(BirthdayGreetingSendVO vo) {
try {
userBirthdayService.sendBlessing(vo);
return Result.success();
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
}
/**
* 查询当前用户收到的祝福。
*/
@At
@SaCheckLogin
public Result giftInfo(UserBirthdayPageVO pageForm, String username) {
return Result.success(userBirthdayService.giftInfo(pageForm, username));
}
/**
* 查询单条贺卡祝福内容。
*/
@At
@SaCheckLogin
public Result findOne(String id) {
return Result.success(userBirthdayService.findGreetingInfo(id));
}
}
@@ -0,0 +1,118 @@
package com.budwk.app.zhgh.staffmanage.birthday.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.staffmanage.birthday.model.UserBirthdayConfig;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayService;
import com.budwk.app.zhgh.staffmanage.birthday.vo.UserBirthdayPageVO;
import com.budwk.app.zhgh.staffmanage.birthday.vo.UserBirthdaySendMsgVO;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import io.swagger.annotations.Api;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 生日祝福管理控制器,提供 PC 管理端列表、配置、发送和导出接口。
*/
@IocBean
@Ok("json:full")
@At("/platform/staffManage/birthday/manage")
@Api(tags = "生日祝福管理")
public class UserBirthdayManageController {
@Inject
private UserBirthdayService userBirthdayService;
/**
* PC 端生日祝福管理页。
*/
@At("")
@Ok("beetl:/platform/zhgh/staffmanage/birthday/manage/index.html")
@SaCheckPermission("staffManage.birthday.manage")
public void index() {
}
/**
* H5 生日贺卡页,文件 ID 从消息链接透传给页面。
*/
@At("/h5")
@Ok("beetl:/platform/zhghh5/staffmanage/birthday/index.html")
@SaCheckLogin
public void h5(String id, HttpServletRequest request) {
// H5 页面需要服务端注入默认值,避免模板变量缺失时 Beetl 渲染失败。
request.setAttribute("fileId", id == null ? "" : id);
request.setAttribute("userName", SecurityUtil.getUserUsername());
}
/**
* 查询生日人员分页数据。
*/
@At
@SaCheckPermission("staffManage.birthday.manage")
public Result pageData(UserBirthdayPageVO page) {
return Result.success(userBirthdayService.pageBirthdayUsers(page));
}
/**
* 导出生日人员列表。
*/
@At
@Ok("void")
@SaCheckPermission("staffManage.birthday.manage")
public void doExport(UserBirthdayPageVO page, HttpServletResponse response) {
userBirthdayService.exportXlsx(page, response);
}
/**
* 按当前查询条件批量发送生日祝福。
*/
@At
@SaCheckPermission("staffManage.birthday.manage")
public Result sendMsgByQueryUsers(UserBirthdaySendMsgVO vo) {
try {
userBirthdayService.sendMsgByQueryUsers(vo);
return Result.success();
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
}
/**
* 给指定职工发送生日祝福。
*/
@At
@SaCheckPermission("staffManage.birthday.manage")
public Result sendMsgByUser(UserBirthdaySendMsgVO vo) {
try {
userBirthdayService.sendMsgByUser(vo);
return Result.success();
} catch (RuntimeException e) {
return Result.error(e.getMessage());
}
}
/**
* 获取生日贺卡配置,PC 管理端和 H5 展示页均会读取。
*/
@At
@SaCheckLogin
public Result getConfig() {
return Result.success(userBirthdayService.getConfig());
}
/**
* 保存生日贺卡配置。
*/
@At
@SaCheckPermission("staffManage.birthday.manage")
public Result saveOrModifyConfig(UserBirthdayConfig config) {
userBirthdayService.saveOrModifyConfig(config);
return Result.success();
}
}
@@ -0,0 +1,42 @@
package com.budwk.app.zhgh.staffmanage.birthday.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayMsgLogService;
import com.budwk.app.zhgh.staffmanage.birthday.vo.UserBirthdayMsgLogPageVO;
import io.swagger.annotations.Api;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
/**
* 生日祝福消息日志控制器。
*/
@IocBean
@Ok("json:full")
@At("/platform/staffManage/birthday/msgLog")
@Api(tags = "生日祝福消息日志")
public class UserBirthdayMsgLogController {
@Inject
private UserBirthdayMsgLogService userBirthdayMsgLogService;
/**
* PC 端生日祝福发送日志页。
*/
@At("")
@Ok("beetl:/platform/zhgh/staffmanage/birthday/msglog/index.html")
@SaCheckPermission("staffManage.birthday.msgLog")
public void index() {
}
/**
* 查询生日祝福发送日志分页数据。
*/
@At
@SaCheckPermission("staffManage.birthday.msgLog")
public Result pageData(UserBirthdayMsgLogPageVO page) {
return Result.success(userBirthdayMsgLogService.pageMsgLogs(page));
}
}
@@ -0,0 +1,54 @@
package com.budwk.app.zhgh.staffmanage.birthday.model;
import com.budwk.app.sys.models.Sys_file;
import lombok.Data;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.List;
/**
* 生日祝福墙礼物配置,用于移动端选择祝福礼物。
*/
@Data
@Table("blessing_gift")
@Comment("生日祝福墙礼物")
public class BlessingGift {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("礼物名称")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String giftName;
@Column
@Comment("礼物文件名称")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String fileName;
@Column
@Comment("礼物图片")
@ColDefine(type = ColType.MYSQL_JSON)
private List<Sys_file> files;
@Column
@Comment("排序字段")
@ColDefine(type = ColType.VARCHAR, width = 10)
private String sortField;
@Column
@Comment("创建人")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String createBy;
@Column
@Comment("创建时间")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String createDate;
}
@@ -0,0 +1,41 @@
package com.budwk.app.zhgh.staffmanage.birthday.model;
import lombok.Data;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
/**
* 生日祝福墙人员中间表,保存当期可被赠送祝福的生日人员。
*/
@Data
@Table("greeting")
@Comment("生日祝福墙人员")
public class Greeting {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("生日月日,格式MMdd")
@ColDefine(type = ColType.VARCHAR, width = 4)
private String birthday;
@Column
@Comment("职工ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String userId;
@Column
@Comment("职工姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String username;
@Column
@Comment("职工工号")
@ColDefine(type = ColType.VARCHAR, width = 120)
private String loginname;
}
@@ -0,0 +1,51 @@
package com.budwk.app.zhgh.staffmanage.birthday.model;
import lombok.Data;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
/**
* 生日祝福墙赠礼记录,保存赠送人与接收人的祝福内容。
*/
@Data
@Table("greeting_info")
@Comment("生日祝福墙赠礼记录")
public class GreetingInfo {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("赠送人ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String presenter;
@Column
@Comment("接收人ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String received;
@Column
@Comment("礼物ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String giftId;
@Column
@Comment("赠送时间")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String giftTime;
@Column
@Comment("祝福语")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String message;
@Column
@Comment("是否发送贺卡")
@ColDefine(type = ColType.BOOLEAN)
private Boolean isSend;
}
@@ -0,0 +1,29 @@
package com.budwk.app.zhgh.staffmanage.birthday.model;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
/**
* 生日祝福配置,保存移动端贺卡展示图等全局配置。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("user_birthday_config")
@Comment("生日祝福配置")
public class UserBirthdayConfig extends BaseModel {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("生日贺卡文件ID")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String birthdayUrl;
}
@@ -0,0 +1,69 @@
package com.budwk.app.zhgh.staffmanage.birthday.model;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
/**
* 生日祝福消息发送日志,记录每次发送到个人的消息内容和链接。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("user_birthday_msg_log")
@Comment("生日祝福消息日志")
public class UserBirthdayMsgLog extends BaseModel {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("推送时间")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String pushTime;
@Column
@Comment("消息标题")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String msgTitle;
@Column
@Comment("消息内容")
@ColDefine(type = ColType.TEXT)
private String msgContent;
@Column
@Comment("接收人")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String receiveBy;
@Column
@Comment("接收人姓名")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String receiveName;
@Column
@Comment("推送人")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String pushBy;
@Column
@Comment("推送人姓名")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String pushByName;
@Column
@Comment("推送类型")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String pushType;
@Column
@Comment("卡片链接")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String link;
}
@@ -0,0 +1,34 @@
package com.budwk.app.zhgh.staffmanage.birthday.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.staffmanage.birthday.model.UserBirthdayMsgLog;
import com.budwk.app.zhgh.staffmanage.birthday.vo.UserBirthdayMsgLogPageVO;
import org.nutz.lang.util.NutMap;
import java.util.List;
/**
* 生日祝福消息日志服务。
*/
public interface UserBirthdayMsgLogService extends BaseService<UserBirthdayMsgLog> {
/**
* 分页查询生日祝福消息日志,并在查询层补齐页面展示所需的人员和推送字段别名。
*
* @param page 日志查询分页参数
* @return 消息日志分页数据
*/
Pagination pageMsgLogs(UserBirthdayMsgLogPageVO page);
/**
* 批量记录生日消息发送日志。
*
* @param users 接收人列表,至少包含 loginname 和 username
* @param title 消息标题
* @param content 消息内容
* @param imageUrl 消息图片
* @param link 跳转链接
*/
void insertLogs(List<NutMap> users, String title, String content, String imageUrl, String link);
}
@@ -0,0 +1,120 @@
package com.budwk.app.zhgh.staffmanage.birthday.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.zhgh.staffmanage.birthday.model.UserBirthdayConfig;
import com.budwk.app.zhgh.staffmanage.birthday.vo.BirthdayGreetingSendVO;
import com.budwk.app.zhgh.staffmanage.birthday.vo.UserBirthdayPageVO;
import com.budwk.app.zhgh.staffmanage.birthday.vo.UserBirthdaySendMsgVO;
import org.nutz.dao.sql.Sql;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 生日祝福业务服务,统一承载 PC 管理端、H5 贺卡、移动端祝福墙所需业务。
*/
public interface UserBirthdayService extends BaseService<Sys_user> {
/**
* 构造生日人员通用查询 SQL。
*
* @param pageVO 查询参数
* @return 可直接执行的 Nutz SQL
*/
Sql commonSql(UserBirthdayPageVO pageVO);
/**
* 查询生日人员分页数据。
*
* @param pageVO 查询参数
* @return 分页结果
*/
Pagination pageBirthdayUsers(UserBirthdayPageVO pageVO);
/**
* 导出生日人员列表。
*
* @param pageVO 查询参数
* @param response 下载响应
*/
void exportXlsx(UserBirthdayPageVO pageVO, HttpServletResponse response);
/**
* 按当前筛选条件批量发送生日祝福。
*
* @param vo 发送参数和筛选条件
*/
void sendMsgByQueryUsers(UserBirthdaySendMsgVO vo);
/**
* 给指定职工发送生日祝福。
*
* @param vo 发送参数
*/
void sendMsgByUser(UserBirthdaySendMsgVO vo);
/**
* 获取当前生日贺卡配置,若不存在则返回空配置对象。
*
* @return 生日配置
*/
UserBirthdayConfig getConfig();
/**
* 保存或更新生日贺卡配置。
*
* @param config 生日配置
*/
void saveOrModifyConfig(UserBirthdayConfig config);
/**
* 查询移动端祝福墙最近生日人员。
*
* @param pageVO 分页参数
* @param searchKeyword 姓名或工号关键字
* @return 人员列表
*/
List<NutMap> listGreetingUsers(UserBirthdayPageVO pageVO, String searchKeyword);
/**
* 查询已勾选的祝福对象。
*
* @param greetingResult 逗号分隔的用户 ID
* @return 人员列表
*/
List<NutMap> loadGreetingUsers(String greetingResult);
/**
* 查询祝福礼物列表。
*
* @return 礼物列表
*/
List<NutMap> giftList();
/**
* 保存移动端祝福墙赠礼记录。
*
* @param vo 赠礼参数
*/
void sendBlessing(BirthdayGreetingSendVO vo);
/**
* 查询当前用户收到的祝福礼物。
*
* @param pageVO 分页参数
* @param username 赠送人姓名
* @return 分页结果
*/
Pagination giftInfo(UserBirthdayPageVO pageVO, String username);
/**
* 查询单条祝福记录,用于贺卡详情页展示。
*
* @param id 祝福记录 ID
* @return 祝福记录
*/
NutMap findGreetingInfo(String id);
}
@@ -0,0 +1,101 @@
package com.budwk.app.zhgh.staffmanage.birthday.service.impl;
import cn.hutool.core.date.DateUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.staffmanage.birthday.model.UserBirthdayMsgLog;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayMsgLogService;
import com.budwk.app.zhgh.staffmanage.birthday.vo.UserBirthdayMsgLogPageVO;
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.random.R;
import org.nutz.lang.util.NutMap;
import java.util.List;
/**
* 生日祝福消息日志服务实现。
*/
@IocBean(args = {"refer:dao"})
public class UserBirthdayMsgLogServiceImpl extends BaseServiceImpl<UserBirthdayMsgLog> implements UserBirthdayMsgLogService {
public UserBirthdayMsgLogServiceImpl(Dao dao) {
super(dao);
}
/**
* 查询消息日志列表,通过 SQL 别名适配页面字段,不修改生日日志表或系统人员表结构。
*/
@Override
public Pagination pageMsgLogs(UserBirthdayMsgLogPageVO page) {
Sql sql = Sqls.create("""
SELECT
l.id,
vu.loginname AS loginname,
IFNULL(vu.username, l.receiveName) AS username,
vu.unionName AS unionName,
vu.unitName AS unitName,
l.msgTitle,
l.msgContent,
l.pushByName,
l.pushTime,
l.pushType,
l.link
FROM
user_birthday_msg_log l
LEFT JOIN vw_user vu ON vu.id = l.receiveBy
$condition
""");
Cnd cnd = Cnd.NEW();
page.buildSearch(cnd, "l.", "vu.");
cnd.desc("l.pushTime");
sql.setCondition(cnd);
return listPageMap(page.getPageNumber(), page.getPageSize(), sql);
}
/**
* 批量记录发送日志,便于管理端追踪生日祝福触达情况。
*/
@Override
public void insertLogs(List<NutMap> users, String title, String content, String imageUrl, String link) {
if (users == null || users.isEmpty()) {
return;
}
String pushBy;
String pushByName;
String pushType;
// 根据是否存在当前登录上下文区分手动推送和系统推送,避免定时任务保存日志失败。
try {
pushBy = SecurityUtil.getUserId();
pushByName = SecurityUtil.getUserUsername();
pushType = "\u624b\u52a8\u63a8\u9001";
} catch (Exception e) {
pushBy = "system";
pushByName = "\u7cfb\u7edf\u63a8\u9001";
pushType = "\u7cfb\u7edf\u63a8\u9001";
}
String pushTime = DateUtil.now();
String finalPushBy = pushBy;
String finalPushByName = pushByName;
String finalPushType = pushType;
List<UserBirthdayMsgLog> logs = users.stream().map(user -> {
UserBirthdayMsgLog msgLog = new UserBirthdayMsgLog();
msgLog.setId(R.UU32());
msgLog.setReceiveBy(user.getString("id"));
msgLog.setReceiveName(user.getString("username"));
msgLog.setMsgTitle(title);
msgLog.setMsgContent(content);
msgLog.setPushBy(finalPushBy);
msgLog.setPushByName(finalPushByName);
msgLog.setPushType(finalPushType);
msgLog.setPushTime(pushTime);
msgLog.setLink(link);
return msgLog;
}).toList();
dao().fastInsert(logs);
}
}
@@ -0,0 +1,380 @@
package com.budwk.app.zhgh.staffmanage.birthday.service.impl;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
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.base.utils.CommonDownloadUtil;
import com.budwk.app.sys.models.Sys_msg;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.services.SysMsgService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.web.commons.base.Globals;
import com.budwk.app.zhgh.staffmanage.birthday.model.GreetingInfo;
import com.budwk.app.zhgh.staffmanage.birthday.model.UserBirthdayConfig;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayMsgLogService;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayService;
import com.budwk.app.zhgh.staffmanage.birthday.vo.BirthdayGreetingSendVO;
import com.budwk.app.zhgh.staffmanage.birthday.vo.UserBirthdayPageVO;
import com.budwk.app.zhgh.staffmanage.birthday.vo.UserBirthdaySendMsgVO;
import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Times;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 生日祝福业务服务实现,适配当前项目的用户视图、消息服务和文件预览路径。
*/
@IocBean(args = {"refer:dao"})
public class UserBirthdayServiceImpl extends BaseServiceImpl<Sys_user> implements UserBirthdayService {
public UserBirthdayServiceImpl(Dao dao) {
super(dao);
}
@Inject
private SysMsgService sysMsgService;
@Inject
private UserBirthdayMsgLogService userBirthdayMsgLogService;
/**
* 构造生日人员通用 SQL,按距离下一次生日的天数升序排列。
*/
@Override
public Sql commonSql(UserBirthdayPageVO page) {
Sql sql = Sqls.create("""
SELECT
u.id,
u.loginname,
u.username,
u.sex,
DATE_FORMAT(u.birthday, '%Y-%m-%d') AS birthday,
u.mobile,
u.unitName,
u.unionName,
u.userState,
u.personType,
u.preparedBy,
DATEDIFF(
CASE
WHEN DATE_FORMAT(u.birthday, '%m-%d') >= DATE_FORMAT(NOW(), '%m-%d')
THEN CONCAT(YEAR(NOW()), '-', DATE_FORMAT(u.birthday, '%m-%d'))
ELSE CONCAT(YEAR(NOW()) + 1, '-', DATE_FORMAT(u.birthday, '%m-%d'))
END,
CURDATE()
) AS daysUntilBirthday
FROM
vw_user u
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("u.birthday", "is not", null);
cnd.and("u.member", "=", true);
page.buildSearch(cnd, "u.");
cnd.asc("daysUntilBirthday");
sql.setCondition(cnd);
return sql;
}
/**
* 执行生日人员分页查询。
*/
@Override
public Pagination pageBirthdayUsers(UserBirthdayPageVO pageVO) {
return listPageMap(pageVO.getPageNumber(), pageVO.getPageSize(), commonSql(pageVO));
}
/**
* 按管理端当前筛选条件导出生日人员,强制使用 XSSF 格式。
*/
@Override
public void exportXlsx(UserBirthdayPageVO pageVO, HttpServletResponse response) {
Sql sql = commonSql(pageVO);
List<NutMap> list = listMap(sql);
List<ExcelExportEntity> entities = new ArrayList<>();
entities.add(new ExcelExportEntity("工号", "loginname", 20));
entities.add(new ExcelExportEntity("姓名", "username", 20));
entities.add(new ExcelExportEntity("性别", "sex", 10));
entities.add(new ExcelExportEntity("联系方式", "mobile", 20));
entities.add(new ExcelExportEntity("出生年月", "birthday", 20));
entities.add(new ExcelExportEntity("生日倒计时(天)", "daysUntilBirthday", 20));
entities.add(new ExcelExportEntity("在职状态", "userState", 20));
entities.add(new ExcelExportEntity("人员类型", "personType", 20));
entities.add(new ExcelExportEntity("人员性质", "preparedBy", 20));
entities.add(new ExcelExportEntity("所属工会", "unionName", 20));
entities.add(new ExcelExportEntity("所属单位", "unitName", 20));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list);
CommonDownloadUtil.download("生日祝福人员.xlsx", workbook, response);
}
/**
* 按筛选条件发送生日祝福,并写入发送日志。
*/
@Override
public void sendMsgByQueryUsers(UserBirthdaySendMsgVO vo) {
validateSendParam(vo);
List<NutMap> users = listMap(commonSql(vo));
if (CollUtil.isEmpty(users)) {
throw new RuntimeException("未查询到消息接收人");
}
sendBirthdayMessage(users, vo.getTitle(), vo.getContent());
}
/**
* 给单个用户发送生日祝福,并写入发送日志。
*/
@Override
public void sendMsgByUser(UserBirthdaySendMsgVO vo) {
validateSendParam(vo);
if (StrUtil.isBlank(vo.getUserId())) {
throw new RuntimeException("未获取到消息接收人");
}
Sql sql = Sqls.create("""
SELECT
id,
loginname,
username
FROM
vw_user
WHERE
id = @id
""").setParam("id", vo.getUserId());
List<NutMap> users = listMap(sql);
if (CollUtil.isEmpty(users)) {
throw new RuntimeException("未获取到消息接收人");
}
sendBirthdayMessage(users, vo.getTitle(), vo.getContent());
}
/**
* 获取最新生日配置,没有配置时返回空对象,便于前端直接绑定。
*/
@Override
public UserBirthdayConfig getConfig() {
UserBirthdayConfig config = dao().fetch(UserBirthdayConfig.class, Cnd.NEW().desc("updatedAt"));
return ObjectUtil.defaultIfNull(config, new UserBirthdayConfig());
}
/**
* 保存生日配置,存在 ID 时更新,否则新增。
*/
@Override
public void saveOrModifyConfig(UserBirthdayConfig config) {
if (StrUtil.isBlank(config.getId())) {
dao().insert(config);
} else {
dao().updateIgnoreNull(config);
}
}
/**
* 查询祝福墙最近生日人员,排除当前登录人,避免自己给自己送祝福。
*/
@Override
public List<NutMap> listGreetingUsers(UserBirthdayPageVO pageVO, String searchKeyword) {
Sql sql = Sqls.create("""
SELECT
g.id,
g.birthday,
g.userId,
g.username,
g.loginname,
u.unitName
FROM
greeting g
LEFT JOIN vw_user u ON g.userId = u.id
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.where().and("g.userId", "!=", SecurityUtil.getUserId());
if (StrUtil.isNotBlank(searchKeyword)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("g.username", searchKeyword);
group.orLike("g.loginname", searchKeyword);
cnd.and(group);
}
cnd.asc("g.birthday");
sql.setCondition(cnd);
return listMap(sql);
}
/**
* 根据移动端勾选结果查询祝福对象。
*/
@Override
public List<NutMap> loadGreetingUsers(String greetingResult) {
List<String> userIds = splitIds(greetingResult);
if (CollUtil.isEmpty(userIds)) {
return List.of();
}
Sql sql = Sqls.create("""
SELECT
id,
birthday,
userId,
username,
loginname
FROM
greeting
$condition
""");
sql.setCondition(Cnd.where("userId", "in", userIds));
return listMap(sql);
}
/**
* 查询祝福墙礼物列表。
*/
@Override
public List<NutMap> giftList() {
Sql sql = Sqls.create("""
SELECT
bg.*
FROM
blessing_gift bg
$condition
""");
sql.setCondition(Cnd.NEW().asc("bg.sortField"));
return listMap(sql);
}
/**
* 保存移动端赠礼记录,逐个接收人生成一条祝福数据。
*/
@Override
public void sendBlessing(BirthdayGreetingSendVO vo) {
List<String> userIds = splitIds(vo.getGreetingResult());
if (CollUtil.isEmpty(userIds)) {
throw new RuntimeException("请选择祝福对象");
}
String currentUserId = SecurityUtil.getUserId();
String today = DateUtil.formatDate(new java.util.Date());
List<GreetingInfo> greetingInfos = userIds.stream().map(userId -> {
GreetingInfo greetingInfo = new GreetingInfo();
greetingInfo.setPresenter(currentUserId);
greetingInfo.setReceived(userId);
greetingInfo.setGiftId(vo.getGiftId());
greetingInfo.setGiftTime(today);
greetingInfo.setMessage(vo.getMessage());
greetingInfo.setIsSend(Boolean.TRUE.equals(vo.getIsSend()));
return greetingInfo;
}).toList();
dao().insert(greetingInfos);
}
/**
* 查询当前用户收到的祝福礼物,支持按赠送人姓名模糊筛选。
*/
@Override
public Pagination giftInfo(UserBirthdayPageVO pageVO, String username) {
Sql sql = Sqls.create("""
SELECT
gi.id,
su.username,
bg.files,
gi.giftTime,
gi.message,
su.unitName AS name,
gi.isSend
FROM
greeting_info gi
LEFT JOIN vw_user su ON gi.presenter = su.id
LEFT JOIN blessing_gift bg ON gi.giftId = bg.id
$condition
""");
Cnd cnd = Cnd.where("gi.received", "=", SecurityUtil.getUserId());
if (StrUtil.isNotBlank(username)) {
cnd.where().andLike("su.username", username);
}
cnd.desc("gi.giftTime");
sql.setCondition(cnd);
return listPageMap(pageVO.getPageNumber(), pageVO.getPageSize(), sql);
}
/**
* 查询单条祝福记录,供贺卡详情页展示赠送人和祝福语。
*/
@Override
public NutMap findGreetingInfo(String id) {
Sql sql = Sqls.create("""
SELECT
su.username,
gi.message
FROM
greeting_info gi
LEFT JOIN vw_user su ON gi.presenter = su.id
WHERE
gi.id = @id
""").setParam("id", id);
List<NutMap> list = listMap(sql);
return CollUtil.isEmpty(list) ? NutMap.NEW() : list.get(0);
}
/**
* 校验消息标题和内容,避免生成空消息。
*/
private void validateSendParam(UserBirthdaySendMsgVO vo) {
if (StrUtil.isBlank(vo.getTitle())) {
throw new RuntimeException("请输入标题");
}
if (StrUtil.isBlank(vo.getContent())) {
throw new RuntimeException("请输入发送内容");
}
}
/**
* 统一发送生日消息并记录日志,保证单人和批量发送行为一致。
*/
private void sendBirthdayMessage(List<NutMap> users, String title, String content) {
UserBirthdayConfig config = getConfig();
String link = Globals.AppDomain + "/platform/staffManage/birthday/manage/h5?id=" + StrUtil.blankToDefault(config.getBirthdayUrl(), "");
List<String> loginNames = users.stream().map(user -> user.getString("loginname")).filter(StrUtil::isNotBlank).distinct().toList();
if (CollUtil.isEmpty(loginNames)) {
throw new RuntimeException("未获取到有效消息接收人");
}
Sys_msg sysMsg = new Sys_msg();
sysMsg.setTitle(title);
sysMsg.setNote(content);
sysMsg.setUrl(link);
sysMsg.setType("user");
sysMsg.setSendType("hide");
sysMsg.setWechatEnterprise(true);
sysMsg.setSendAt(Times.getTS());
sysMsg.setCreatedBy(SecurityUtil.getUserId());
sysMsgService.saveMsg(sysMsg, loginNames.toArray(String[]::new), true);
userBirthdayMsgLogService.insertLogs(users, title, content, "", link);
}
/**
* 拆分移动端传入的逗号分隔 ID,过滤空值。
*/
private List<String> splitIds(String ids) {
if (StrUtil.isBlank(ids)) {
return List.of();
}
return Arrays.stream(ids.split(",")).filter(StrUtil::isNotBlank).distinct().toList();
}
}
@@ -0,0 +1,22 @@
package com.budwk.app.zhgh.staffmanage.birthday.vo;
import lombok.Data;
/**
* 移动端祝福墙赠礼参数。
*/
@Data
public class BirthdayGreetingSendVO {
/**
* 逗号分隔的接收人 ID 列表。
*/
private String greetingResult;
/**
* 礼物 ID、祝福语和是否同步发送贺卡。
*/
private String giftId;
private String message;
private Boolean isSend;
}
@@ -0,0 +1,64 @@
package com.budwk.app.zhgh.staffmanage.birthday.vo;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.param.PageForm;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.Cnd;
import org.nutz.dao.util.cri.SqlExpressionGroup;
/**
* 生日祝福消息日志查询参数。
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class UserBirthdayMsgLogPageVO extends PageForm {
/**
* 按接收人工号、姓名、标题进行模糊检索。
*/
private String keyword;
private String unionId;
private String unitId;
/**
* 构造日志列表查询条件。
*
* @param cnd Nutz 查询条件对象
*/
public void buildSearch(Cnd cnd) {
buildSearch(cnd, "", "");
}
/**
* 构造日志列表查询条件,prefix 用于适配带表别名的 SQL 查询。
*
* @param cnd Nutz 查询条件对象
* @param prefix 字段前缀,例如 l.
*/
public void buildSearch(Cnd cnd, String prefix) {
buildSearch(cnd, prefix, "");
}
/**
* 构造日志列表查询条件,分别适配日志表别名和人员视图别名。
*
* @param cnd Nutz 查询条件对象
* @param logPrefix 日志表字段前缀,例如 l.
* @param userPrefix 人员视图字段前缀,例如 vu.
*/
public void buildSearch(Cnd cnd, String logPrefix, String userPrefix) {
String alias = StrUtil.blankToDefault(logPrefix, "");
String key = StrUtil.blankToDefault(keyword, getSearchKeyword());
if (StrUtil.isNotBlank(key)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike(StrUtil.blankToDefault(userPrefix, "") + "loginname", key);
group.orLike(alias + "receiveName", key);
group.orLike(alias + "msgTitle", key);
cnd.and(group);
}
String userAlias = StrUtil.blankToDefault(userPrefix, "");
cnd.andEX(userAlias + "unionId", "=", unionId);
cnd.andEX(userAlias + "unitId", "=", unitId);
}
}
@@ -0,0 +1,92 @@
package com.budwk.app.zhgh.staffmanage.birthday.vo;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.param.PageForm;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.Cnd;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.dao.util.cri.Static;
import org.nutz.json.Json;
import java.util.ArrayList;
import java.util.List;
/**
* 生日人员分页查询参数,统一承载 PC 列表、导出和批量发送的筛选条件。
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class UserBirthdayPageVO extends PageForm {
/**
* 按工号、姓名、手机号进行模糊检索。
*/
private String keyword;
/**
* 指定工会、单位、人员类型等筛选条件。
*/
private String unionId;
private String unitId;
private String personType;
private String userState;
private String sex;
private String preparedBy;
private String userStates;
private String personTypes;
private String preparedBys;
/**
* 生日月份,支持一次查询多个月份。
*/
private List<Integer> birthMonths;
/**
* 构造生日人员查询条件,prefix 用于适配不同 SQL 表别名。
*
* @param cnd Nutz 查询条件对象
* @param prefix 字段前缀,例如 u.
*/
public void buildSearch(Cnd cnd, String prefix) {
String alias = StrUtil.blankToDefault(prefix, "");
String key = StrUtil.blankToDefault(keyword, getSearchKeyword());
if (StrUtil.isNotBlank(key)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike(alias + "loginname", key);
group.orLike(alias + "username", key);
group.orLike(alias + "mobile", key);
cnd.and(group);
}
cnd.andEX(alias + "unionId", "=", unionId);
cnd.andEX(alias + "unitId", "=", unitId);
cnd.andEX(alias + "personType", "=", personType);
cnd.andEX(alias + "personType", "in", parseMultiValues(personTypes));
cnd.andEX(alias + "userState", "=", userState);
cnd.andEX(alias + "userState", "in", parseMultiValues(userStates));
cnd.andEX(alias + "sex", "=", sex);
cnd.andEX(alias + "preparedBy", "=", preparedBy);
cnd.andEX(alias + "preparedBy", "in", parseMultiValues(preparedBys));
if (CollUtil.isNotEmpty(birthMonths)) {
cnd.and(new Static("MONTH(" + alias + "birthday) IN (" + CollUtil.join(birthMonths, ",") + ")"));
}
}
/**
* 将页面多选组件传入的 JSON 数组字符串转换为查询条件列表,兼容生日模块迁移后的前端字段命名。
*/
private List<String> parseMultiValues(String value) {
if (StrUtil.isBlank(value)) {
return null;
}
try {
List<String> values = Json.fromJsonAsList(String.class, value);
return CollUtil.isEmpty(values) ? null : values;
} catch (RuntimeException e) {
List<String> values = new ArrayList<>();
values.add(value);
return values;
}
}
}
@@ -0,0 +1,23 @@
package com.budwk.app.zhgh.staffmanage.birthday.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 生日祝福发送参数,支持单人发送和按当前查询条件批量发送。
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class UserBirthdaySendMsgVO extends UserBirthdayPageVO {
/**
* 单人发送时接收人的用户 ID。
*/
private String userId;
/**
* 消息标题和内容。
*/
private String title;
private String content;
}
+177
View File
@@ -0,0 +1,177 @@
nutz:
application:
name: zhgh_jshvc
mvc:
ignore: ^(.+[.])(jsp|png|gif|jpg|js|css|jspx|jpeg|html|mp3|mp4|ico|svg|vue)$
exclusions: /favicon/*,/assets/*,/druid/*,/upload/*,/components/*
server:
port: 80
host: 0.0.0.0
jetty:
contextPath: /
threadpool:
idleTimeout: 60000
minThreads: 10
maxThreads: 500
page:
403: /error/403.html
404: /error/404.html
500: /error/500.html
#结合ftp使用,或用nginx代理ftp路径
#staticPath: /Users/wizzer/temp/files
#开发模式静态资源
staticPathLocal: E:/projects/zhgh_jshvc/src/main/resources/static
security:
tokenName: saToken
timeout: 36000
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
# 允许同一账号并发登录,则系统参数 SessionOnlyOne 有效,亦可踢人下线并多了弹框提示功能
isConcurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
isShare: false
wxwork:
enable: true
corpId: wwaca81b3016b9a58c
corpSecret: Auc7919Su1Uds5ZErfZybBrt-8FkP5NA-dldZyQuOrM
agentId: 1000131
redirectUri : /platform/wxwork/oauth2/callback
wx:
enable: false
appID: wx4f65ff5f1b72e99d
appSecret: a65cec4fb69980bd88258fc8433a3eef
redirectUri: /platform/wx/oauth2/callback
cas:
enable: false
server-url-prefix: https://i.njupt.edu.cn/cas
server-login-url: https://i.njupt.edu.cn/cas/login
client-host-url: https://zhgh.njupt.edu.cn
client-call-back-url: /platform/sso/login
validation-type: cas
todo-platform:
token-url: /accessToken
base-url: https://gateway.jshvc.edu.cn
save-or-update-url: /saveOrUpdate
modify-url: /modifyInfo
delete-task-url: /delete
delete-instance-url: /realDelete
app-id: XXXXXXXXXXXXXXXXXXXXXX
app-secret: XXXXXXXXXXXXXXXXXXXXXX
request-timeout: 5000
redis:
host: 127.0.0.1
port: 6379
timeout: 2000
max_redir: 10
database: 1
maxTotal: 500
pool:
maxTotal: 500
maxIdle: 50
minIdle: 10
#password: test123
mode: normal
#cluster
#nodes=192.168.6.31:6377,192.168.6.31:6378,192.168.6.28:6377,192.168.6.28:6378,192.168.6.34:6377,192.168.6.34:6378
jdbc:
url: jdbc:mysql://192.168.21.212:3306/zhgh_jshvc?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: 123456
validationQuery: select 1
maxActive: 1000
testWhileIdle: true
connectionProperties: druid.stat.slowSqlMillis=2000
defaultAutoCommit: true
xsssql:
ignore:
urls: /platform/cms/content/article/addDo,/platform/sys/app/conf/addDo,/platform/sys/app/conf/editDo
beetl:
RESOURCE:
#本地路径,开发时设置本地路径,便于调试
rootLocal: E:/projects/zhgh_jshvc/src/main/resources/views/
root: views/
DELIMITER_STATEMENT_START: "<!--#"
DELIMITER_STATEMENT_END: "#-->"
FT:
#用法: ${"wizzer",escape}
escape: com.budwk.app.web.commons.ext.beetl.HtmlEscapeFormat
#用法: ${10241024,filesize}
filesize: com.budwk.app.web.commons.ext.beetl.FileSizeFormat
#用法: ${"",html2txt="100"} 截取100字符
html2txt: com.budwk.app.web.commons.ext.beetl.Html2TxtFormat
#用法: ${"",strlen="100"} 截取100字符
strlen: com.budwk.app.web.commons.ext.beetl.StrlenFormat
#==============================================================
#Configure Main Scheduler Properties
#==============================================================
#quartz延迟启动秒数
quartz:
startupDelay: 10
scheduler:
instanceName: defaultScheduler
instanceId: AUTO
#==============================================================
#Skip Check Update
#update:true
#not update:false
#==============================================================
skipUpdateCheck: true
#==============================================================
#Configure JobStore isClustered=启用集群模式
#==============================================================
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
#driverDelegateClass: org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
#Other delegates can see: http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/tutorial-lesson-09.html
dataSource: myDS
tablePrefix: SYS_QRTZ_
isClustered: false
clusterCheckinInterval: 20000
maxMisfiresToHandleAtATime: 120
misfireThreshold: 120000
txIsolationLevelSerializable: false
#==============================================================
#Configure ThreadPool
#==============================================================
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 2
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
#============================================================================
# Configure Plugins
#============================================================================
plugin:
triggHistory:
class: org.quartz.plugins.history.LoggingJobHistoryPlugin
shutdownhook:
class: org.quartz.plugins.management.ShutdownHookPlugin
cleanShutdown: true
#============================================================================
# NutDao dataSource
#============================================================================
dataSource:
myDS:
connectionProvider:
class: com.budwk.app.task.conn.NutConnectionProvider
minio:
accessKey: minioadmin
secretKey: minioadmin
endPoint: http://192.168.21.214:9000
bucket: njupt
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,430 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="204px" height="204px" viewBox="0 0 204 204" enable-background="new 0 0 204 204" xml:space="preserve"> <image id="image0" width="204" height="204" x="0" y="0"
href="data:image/png;base64,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" />
</svg>

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -477,10 +477,51 @@
.v4-user-section {
display: flex;
align-items: center;
gap: 24px;
gap: 18px;
height: 100%;
max-width: 355px;
max-width: 520px;
flex-shrink: 0;
padding-right: 16px;
}
.v4-retire-system-link {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 90px;
height: 30px;
padding: 0 14px;
border-radius: 18px;
color: #ffffff;
font-size: 12px;
font-weight: 700;
line-height: 1;
text-decoration: none;
white-space: nowrap;
background: linear-gradient(90deg, #f5a300 0%, #ff8e00 42%, #ff4b16 100%);
border: 1px solid rgba(255, 247, 184, 0.9);
box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.48), 0 2px 6px rgba(141, 53, 0, 0.25);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.v4-retire-system-link::before {
content: "";
position: absolute;
top: 3px;
left: 8px;
right: 8px;
height: 10px;
border-radius: 999px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.62), rgba(255, 255, 255, 0));
pointer-events: none;
}
.v4-retire-system-link:hover {
color: #ffffff;
text-decoration: none;
transform: translateY(-1px);
box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.55), 0 3px 9px rgba(141, 53, 0, 0.32);
}
.v4-notification {
@@ -613,7 +654,12 @@
}
.v4-user-section {
gap: 16px;
gap: 14px;
}
.v4-retire-system-link {
min-width: 78px;
padding: 0 10px;
}
}
@@ -692,6 +738,9 @@
</div>
<div class="v4-user-section">
<a class="v4-retire-system-link" href="http://192.168.73.133:8081/platform/login">
离退休系统
</a>
<div class="v4-user-info">
<span class="v4-user-name">${@auth.getPrincipalProperty('username')} (${@auth.getPrincipalProperty('loginname')})</span>
<!-- <i class="fa fa-angle-down"></i> -->
@@ -0,0 +1,154 @@
const COMMON_QUERY = {
template: [
'',
' <div class="search">',
' <div class="search-item">',
' <div class="search-item-label">姓名/工号</div>',
' <div class="search-item-option">',
' <el-input placeholder="请输入姓名或工号查询" clearable v-model="pageForm.searchKeyword"></el-input>',
' </div>',
' </div>',
' <div class="search-item">',
' <div class="search-item-label">所属工会</div>',
' <div class="search-item-option">',
' <el-select @change="flushUnits" @clear="flushUnits" clearable filterable',
' placeholder="请选择所属工会" style="width: 100%;" v-model="pageForm.unionId">',
' <el-option :key="item.id" :label="item.unionname" :value="item.id"',
' v-for="item in unions"></el-option>',
' </el-select>',
' </div>',
' </div>',
' <div class="search-item">',
' <div class="search-item-label">所属单位</div>',
' <div class="search-item-option">',
' <el-select clearable filterable placeholder="请选择所属单位" style="width: 100%"',
' v-model="pageForm.unitId">',
' <el-option :key="item.id" :label="item.name" :value="item.id"',
' v-for="item in units"></el-option>',
' </el-select>',
' </div>',
' </div>',
' <div class="search-item">',
' <div class="search-item-label">在职状态</div>',
' <div class="search-item-option">',
' <dict-select v-model="pageForm.userStates" style="width: 100%" placeholder="在职状态" @change="doSearch"',
' code="USER_STATE" multiple></dict-select>',
' </div>',
' </div>',
' <div class="search-item">',
' <div class="search-item-label">人员类型</div>',
' <div class="search-item-option">',
' <dict-select v-model="pageForm.personTypes" style="width: 100%" placeholder="人员类型" @change="doSearch"',
' code="PERSON_TYPE" multiple></dict-select>',
' </div>',
' </div>',
' <div class="search-item">',
' <div class="search-item-label">人员性质</div>',
' <div class="search-item-option">',
' <dict-select v-model="pageForm.preparedBys" style="width: 100%" placeholder="人员性质" @change="doSearch"',
' code="PREPARED_BY" multiple></dict-select>',
' </div>',
' </div>',
' <div class="search-item">',
' <div class="search-item-label">生日日期</div>',
' <div class="search-item-option">',
' <el-date-picker :picker-options="pickerOptions" @change="changeDateRangeChange"',
' align="right" end-placeholder="结束日期" format="yyyy-MM-dd"',
' range-separator="-" start-placeholder="开始日期" style="width: 100%"',
' type="datetimerange" v-model="pageForm.changeDateRange"',
' value-format="yyyy-MM-dd"></el-date-picker>',
' </div>',
' </div>',
' <div class="search-query">',
' <el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>',
' </div>',
' </div>',
' '
].join(''),
data() {
return {
pickerOptions: {
shortcuts: [
{
text: "未来一周",
onClick(picker) {
const end = new Date()
const start = new Date()
end.setTime(start.getTime() + 3600 * 1000 * 24 * 7)
picker.$emit("pick", [start, end])
}
},
{
text: "未来一个月",
onClick(picker) {
const end = new Date()
const start = new Date()
end.setTime(start.getTime() + 3600 * 1000 * 24 * 30)
picker.$emit("pick", [start, end])
}
},
{
text: "未来三个月",
onClick(picker) {
const end = new Date()
const start = new Date()
end.setTime(start.getTime() + 3600 * 1000 * 24 * 90)
picker.$emit("pick", [start, end])
}
}
]
},
unions: [],
units: [],
pageForm: {
searchKeyword: '',
unionId: '',
unitId: '',
userStates: [],
personTypes: [],
preparedBys: [],
changeDateRange: [],
}
}
},
methods: {
doSearch(){
const pageForm = clone(this.pageForm)
pageForm.userStates = JSON.stringify(this.pageForm.userStates)
pageForm.personTypes = JSON.stringify(this.pageForm.personTypes)
pageForm.preparedBys = JSON.stringify(this.pageForm.preparedBys)
this.$emit('search', pageForm)
},
changeDateRangeChange(val){
if (val && val.length > 0) {
this.pageForm.startDate = val[0]
this.pageForm.endDate = val[1]
} else {
this.pageForm.startDate = null
this.pageForm.endDate = null
}
this.doSearch()
},
async initData(){
if (this.$auth.hasRoleOr('sysadmin, SchoolUnionMemberAdmin, SchoolUnionAdmin')) {
this.unions = await getUnions(this.pageForm.unionId)
this.units = await getUnits()
} else {
const user = JSON.parse(window.sessionStorage.getItem('user'))
this.unions = await getUnions(user.union.id)
this.units = await getUnits(user.union.id)
}
},
async flushUnits(){
this.$set(this.pageForm, "unitId", null)
this.units = []
if (this.pageForm.unionId) {
this.units = await getUnits(this.pageForm.unionId)
}
}
},
async created() {
this.initData()
}
}
@@ -0,0 +1,296 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<common-query ref="commonQueryRef" @search="search"></common-query>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="数据列表(默认展示近15天的数据,搜索会忽略年份)">
<el-button type="primary" size="small" @click="doExport">导出Excel</el-button>
<el-button type="primary" size="small" @click="openSendMsgByQuery">发送通知</el-button>
<el-button type="primary" size="small" @click="openPicSet">配置图片</el-button>
</table-tool>
<el-table :data="tableData" :size="tableSize" @sort-change="pageOrder" ref="table"
row-key="id" style="width: 100%">
<el-table-column :index="indexMethod"
header-align="center"
label="序号" type="index" width="80px"></el-table-column>
<el-table-column :label="column.label"
:prop="column.prop"
:sortable="column.sortable"
:width="column.width"
header-align="center"
min-width="100px"
show-overflow-tooltip
v-for="column in tableColumns">
</el-table-column>
<el-table-column fixed="right" label="操作" width="250px">
<template slot-scope="{row}">
<el-button @click="openView(row)" size="mini" type="primary">人员信息</el-button>
<el-button @click="openSendMsgByUser(row)" size="mini" type="primary">发送通知</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #view>
<member-info ref="memberInfoRef"></member-info>
</template>
</guava>
<el-dialog
:close-on-click-modal="false"
:visible.sync="sendMsgByQueryDialogVisible"
title="请在下方填写需要发送的内容"
width="50%"
>
<el-form :model="sendMsgByQueryFormData" label-width="80px" ref="sendMsgByQueryForm">
<el-form-item
:rules="[{required: true, message: '请输入发送标题', trigger: ['blur', 'change']}]"
label="发送标题"
prop="title">
<el-input
:rows="6"
placeholder="请输入发送标题"
v-model="sendMsgByQueryFormData.title">
</el-input>
</el-form-item>
<el-form-item
:rules="[{required: true, message: '请选择发送内容', trigger: ['blur', 'change']}]"
label="发送内容"
prop="content">
<el-input
:rows="6"
placeholder="请输入内容"
type="textarea"
v-model="sendMsgByQueryFormData.content">
</el-input>
</el-form-item>
</el-form>
<span class="dialog-footer" slot="footer">
<el-button @click="sendMsgByQueryDialogVisible = false">取 消</el-button>
<el-button @click="sendMsgByQueryUsers" type="primary">确 定</el-button>
</span>
</el-dialog>
<!-- 根据行内数据发送消息 -->
<el-dialog
:close-on-click-modal="false"
:visible.sync="sendMsgByUserDialogVisible"
title="请在下方填写需要发送的内容"
width="50%"
>
<el-form :model="sendMsgByUserFormData" label-width="80px" ref="sendForm">
<el-form-item label="发送对象" prop="userInfo">
<el-input placeholder="请输入发送对象" readonly v-model="sendMsgByUserFormData.userInfo"></el-input>
</el-form-item>
<el-form-item
:rules="[{required: true, message: '请输入发送标题', trigger: ['blur', 'change']}]"
label="发送标题"
prop="title">
<el-input
placeholder="请输入发送标题"
v-model="sendMsgByUserFormData.title">
</el-input>
</el-form-item>
<el-form-item
:rules="[{required: true, message: '请选择发送内容', trigger: ['blur', 'change']}]"
label="发送内容"
prop="content">
<el-input
:rows="6"
placeholder="请输入内容"
type="textarea"
v-model="sendMsgByUserFormData.content">
</el-input>
</el-form-item>
</el-form>
<span class="dialog-footer" slot="footer">
<el-button @click="sendMsgByUserDialogVisible = false">取 消</el-button>
<el-button @click="sendMsgByUser" type="primary">确 定</el-button>
</span>
</el-dialog>
<el-dialog
:close-on-click-modal="false"
:visible.sync="dialogVisible"
title="生日贺卡图片设置"
width="50%"
>
<el-form :model="formData" label-width="80px" ref="formRef">
<el-form-item label="背景图片" prop="picUrl">
<file-upload :upload_number="1"
:upload_size="1024 * 1024 * 5"
:value.sync="formData.picUrl"
accept=".jpg,.jpeg,.png"
upload_mode="image"
upload_result_category="interval"
upload_result_type="id"></file-upload>
</el-form-item>
<el-form-item label="贺卡图片" prop="birthdayUrl">
<file-upload :upload_number="1"
:upload_size="1024 * 1024 * 5"
:value.sync="formData.birthdayUrl"
accept=".jpg,.jpeg,.png"
upload_mode="image"
upload_result_category="interval"
upload_result_type="id"></file-upload>
</el-form-item>
</el-form>
<span class="dialog-footer" slot="footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button @click="doSubmit" type="primary">确 定</el-button>
</span>
</el-dialog>
</div>
<script>
<!--#include("commonQuery.js"){}#-->
<!--#include("/platform/zhgh/staffmanage/member/common/info/memberInfo.js"){}#-->
new Vue({
el: '#app',
mixins: [initTableMixins],
data() {
return {
pageForm: {},
tableColumns: [
{ prop: "loginname", label: "工号", sortable: true },
{ prop: "username", label: "姓名", sortable: true },
{ prop: "sex", label: "性别", sortable: true },
{ prop: "mobile", label: "联系方式", sortable: true },
{ prop: "birthday", label: "出生年月", sortable: true },
{ prop: "daysUntilBirthday", label: "生日倒计时(天)", sortable: true },
{ prop: "userState", label: "在职状态", sortable: true, width: "100px" },
{ prop: "personType", label: "人员类型", sortable: true, width: "120px" },
{ prop: "preparedBy", label: "人员性质", sortable: true, width: "120px" },
{ prop: "unionName", label: "所属工会", sortable: true, width: "160px" },
{ prop: "unitName", label: "所属单位", sortable: true, width: "160px" }
],
// 根据查询条件推送消息相关
sendMsgByQueryDialogVisible: false,
sendMsgByQueryFormData: {},
// 根据行内发送消息相关
sendMsgByUserDialogVisible: false,
sendMsgByUserFormData: {},
// 图片配置
dialogVisible: false,
formData: {
picUrl: '',
birthdayUrl: ''
}
}
},
components: {
'common-query': COMMON_QUERY,
'member-info': MEMBER_INFO,
},
methods: {
async openPicSet() {
const resp = await $.post('/platform/staffManage/birthday/manage/getConfig')
if (resp.code === 0) {
this.formData = resp.data
}
this.dialogVisible = true
},
doSubmit() {
this.$confirm('确定要提交吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const resp = await $.post('/platform/staffManage/birthday/manage/saveOrModifyConfig', this.formData)
if (resp.code === 0) {
this.$message.success('保存成功')
this.dialogVisible = false
} else {
this.$message.error(resp.msg)
}
})
},
openSendMsgByUser(row) {
this.sendMsgByUserFormData.userId = row.id
this.sendMsgByUserFormData.userInfo = row.username
this.sendMsgByUserDialogVisible = true
},
async sendMsgByUser() {
const valid = await this.$refs['sendForm'].validate()
if (!valid) return
const confirm = await this.$confirm('您确定要向' + this.sendMsgByUserFormData.userInfo + '数据发送信息吗?', '提示', {
confirmButtonText: '确定',
cancelButtonTest: '取消',
type: 'warning'
})
if (confirm === "confirm") {
const resp = await $.post('/platform/staffManage/birthday/manage/sendMsgByUser',
this.sendMsgByUserFormData
)
if (resp.code === 0) {
this.sendMsgByUserDialogVisible = false
this.$message.success(resp.msg)
} else {
this.$message.warning(resp.msg)
}
}
},
openSendMsgByQuery() {
this.sendMsgByQueryFormData = JSON.parse(JSON.stringify(this.pageForm))
this.sendMsgByQueryDialogVisible = true
},
async sendMsgByQueryUsers() {
const valid = await this.$refs['sendMsgByQueryForm'].validate()
if (!valid) return
const confirm = await this.$confirm('您确定根据查询条件发送信息吗?', '提示', {
confirmButtonText: '确定',
cancelButtonTest: '取消',
type: 'warning'
})
if (confirm === "confirm") {
const resp = await $.post('/platform/staffManage/birthday/manage/sendMsgByQueryUsers', this.sendMsgByQueryFormData)
if (resp.code === 0) {
this.sendMsgByQueryDialogVisible = false
this.$message.success(resp.msg)
} else {
this.$message.warning(resp.msg)
}
}
},
doExport() {
this.$downLoad("/platform/staffManage/birthday/manage/doExport", this.pageForm)
},
openView(row) {
this.$refs.guava.view()
this.$nextTick(() => {
this.$refs.memberInfoRef.onOpen(row.id)
})
},
search(pageForm){
this.pageForm = { ...this.pageForm, ...pageForm }
this.pageData()
},
},
async created() {
this.pageData()
},
})
</script>
<!--#
}
#-->
@@ -0,0 +1,74 @@
const COMMON_QUERY = {
template: [
'',
' <div class="search">',
' <div class="search-item">',
' <div class="search-item-label">姓名/工号</div>',
' <div class="search-item-option">',
' <el-input placeholder="请输入姓名或工号查询" clearable v-model="pageForm.searchKeyword"></el-input>',
' </div>',
' </div>',
' <div class="search-item">',
' <div class="search-item-label">所属工会</div>',
' <div class="search-item-option">',
' <el-select @change="flushUnits" @clear="flushUnits" clearable filterable',
' placeholder="请选择所属工会" style="width: 100%;" v-model="pageForm.unionId">',
' <el-option :key="item.id" :label="item.unionname" :value="item.id"',
' v-for="item in unions"></el-option>',
' </el-select>',
' </div>',
' </div>',
' <div class="search-item">',
' <div class="search-item-label">所属单位</div>',
' <div class="search-item-option">',
' <el-select clearable filterable placeholder="请选择所属单位" style="width: 100%"',
' v-model="pageForm.unitId">',
' <el-option :key="item.id" :label="item.name" :value="item.id"',
' v-for="item in units"></el-option>',
' </el-select>',
' </div>',
' </div>',
' <div class="search-query">',
' <el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>',
' </div>',
' </div>',
' '
].join(''),
data() {
return {
unions: [],
units: [],
pageForm: {
searchKeyword: '',
unionId: '',
unitId: '',
}
}
},
methods: {
doSearch(){
this.$emit('search', this.pageForm)
},
async initData(){
if (this.$auth.hasRoleOr('sysadmin, SchoolUnionMemberAdmin, SchoolUnionAdmin')) {
this.unions = await getUnions(this.pageForm.unionId)
this.units = await getUnits()
} else {
const user = JSON.parse(window.sessionStorage.getItem('user'))
this.unions = await getUnions(user.union.id)
this.units = await getUnits(user.union.id)
}
},
async flushUnits(){
this.$set(this.pageForm, "unitId", null)
this.units = []
if (this.pageForm.unionId) {
this.units = await getUnits(this.pageForm.unionId)
}
}
},
async created() {
this.initData()
}
}
@@ -0,0 +1,78 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app">
<guava ref="guava">
<el-card shadow="never">
<common-query ref="commonQuery" @search="search"></common-query>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool :app="this" label="数据列表">
<template #func>
<!-- <el-button type="primary" size="small" @click="doExport">导出Excel</el-button>-->
</template>
</table-tool>
<el-table :data="tableData" :size="tableSize" @sort-change="pageOrder" ref="table"
row-key="id" style="width: 100%">
<el-table-column :index="indexMethod"
header-align="center"
label="序号" type="index" width="80px"></el-table-column>
<el-table-column :label="column.label"
:prop="column.prop"
:sortable="column.sortable"
:width="column.width"
header-align="center"
min-width="100px"
show-overflow-tooltip
v-for="column in tableColumns">
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
</div>
<script>
<!--#include("commonQuery.js"){}#-->
new Vue({
el: '#app',
mixins: [initTableMixins],
data(){
return{
pageForm: {},
tableColumns: [
{ prop: "loginname", label: "工号", sortable: true },
{ prop: "username", label: "姓名", sortable: true },
{ prop: "unionName", label: "所属工会", sortable: true, width: "160px" },
{ prop: "unitName", label: "所属单位", sortable: true, width: "160px" },
{ prop: "msgTitle", label: "消息标题", sortable: true },
{ prop: "msgContent", label: "消息内容", sortable: true },
{ prop: "pushByName", label: "推送人", sortable: true },
{ prop: "pushTime", label: "推送时间", sortable: true },
{ prop: "pushType", label: "推送类型", sortable: true },
],
}
},
components: {
'common-query': COMMON_QUERY,
},
methods: {
doExport(){
},
search(pageForm){
this.pageForm = { ...this.pageForm, ...pageForm }
this.pageData()
},
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,132 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
.test{width:9em; overflow:hidden; white-space:nowrap;text-overflow: ellipsis;font-size: 10px;text-align: center;color: #a0a1a1
}
.test:hover{text-overflow:inherit; overflow:visible;}
</style>
<div id="app" v-cloak>
<van-nav-bar title="生日祝福" left-arrow placeholder fixed
@click-left="history.back()"></van-nav-bar>
<div class="search-fixed">
<van-search
v-model="pageForm.searchKeyWord"
shape="round"
maxlength="10"
@search="doSearch"
placeholder="请输入教职工姓名或者教职工编号进行查询"
>
</van-search>
<div style="display: flex">
<div style="width: calc(100% - 100px);background: rgb(255,255,255)">
<h4 style="margin-left: 5%;color: rgb(122,110,96)">一份祝福拉进同事的距离~</h4>
<h5 style="margin-left: 5%;color: rgb(122,110,96)">{{tableData.length}}同事即将生日</h5>
</div>
<div style="width: 100px;max-width: 100px">
<van-image width="100px"
height="100px"
src="/assets/mobile/img/birthday/birthday1.png"
></van-image>
</div>
</div>
</div>
<div style="margin-top: 160px;margin-bottom: 50px">
<div>
<van-checkbox-group v-model="result">
<van-grid :column-num="3" :border="false" style="background-color: white">
<van-grid-item
v-for="o in tableData"
:key="o.userId" style="padding: 5px;">
<span style="font-size: 10px">{{o.birthday.substr(0,2)}}-{{o.birthday.substr(2,4)}}</span>
<div style="background-image: url('/assets/mobile/img/birthday/birthday2.png');
width: 100px;
height: 100px;
background-size: 100% 100%;
position: relative;
display: flex;
align-items: center;
justify-content: center;
">
<span style="font-size: 20px;font-weight: bold;font-family: cursive">{{o.username.substr(0,1)}}</span>
</div>
<span style="font-size: 10px">{{o.username}}</span>
<div class="test">
<span>{{o.unitName}}</span>
</div>
<!-- <span v-if="o.unitName.length>7" style="font-size: 10px;">{{o.unitName.substr(0,7)+"..."}}</span>-->
<!-- <span v-else style="font-size: 10px">{{o.unitName}}</span>-->
<van-checkbox
style="margin-left: 80px;margin-top: -35px"
icon-size="20px"
:name="o.userId" ref="checkboxes">
</van-checkbox>
</van-grid-item>
</van-grid>
</van-checkbox-group>
</div>
<div>
<van-tabbar route>
<van-tabbar-item replace @click="sendBlessings()" style="font-size: 16px;">送上祝福</van-tabbar-item>
<van-tabbar-item replace @click="showMine()" style="font-size: 16px">我收到的</van-tabbar-item>
</van-tabbar>
</div>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: '#app',
data() {
return {
tableData: [],
pageForm: {},
checked: true,
result: [],
}
},
methods: {
doSearch() {
this.onLoad()
},
async onLoad() {
const res = await $.post('/mobile/birthday/greeting/pageData', this.pageForm)
if (res.code === 0) {
this.tableData = res.data
}
},
//送上祝福
sendBlessings() {
if(this.result.length===0){
this.$toast.fail("请选择教职工");
return;
}
localStorage.setItem("greetingResult",this.result);
window.location.href = '/mobile/birthday/greeting/greeting_list'
},
toggle(index) {
this.$refs.checkboxes[index].toggle();
},
showMine() {
window.location.href = '/mobile/birthday/greeting/greeting_info'
}
},
created() {
this.onLoad();
},
components: {
},
})
</script>
<!--#
}
#-->
@@ -0,0 +1,134 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
.van-doc-card {
margin: 14px;
padding: 6px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 8px 12px #ebedf0;
line-height: 25px;
font-size: 12px;
}
.van-cell {
padding: 4px 16px;
}
.van-cell-group--inset {
margin: 0;
}
</style>
<div id="app" v-cloak>
<m-page-loading v-if="mLoading"></m-page-loading>
<van-nav-bar title="我收到的祝福" left-arrow placeholder fixed
@click-left="history.back()"></van-nav-bar>
<div class="search-fixed">
<van-search
v-model="pageForm.username"
shape="round"
maxlength="10"
@search="doSearch"
placeholder="请输入教职工姓名进行查询"
>
</van-search>
</div>
<div style="margin-top: 70px">
<van-list v-model="mLoading" :finished="finished" :finished-text="giftInfo.length>0?'没有更多了':''"
@load="onLoad">
<div v-for="o in giftInfo" :key="o.id" class="van-doc-card">
<div>
<div style="font-size: 15px;
font-weight: bold;
flex: 1;
margin-left: 15px;
overflow: hidden;
white-space: nowrap;">
<span style="color: #3ea1ec">{{o.username}}</span>
<span style="color: #3ea1ec">|</span>
<span style="font-size: 10px;font-weight: normal;color: #a0a1a1">{{o.name}}</span>
</div>
</div>
<div style="font-size: 10px">
<van-cell-group inset >
<van-cell :border="false" title="赠送日期">{{o.giftTime}}</van-cell>
<van-cell :border="false" title="祝福语">
<span v-if="o.message.length==0"></span>
<span v-if="o.message.length>0">{{o.message}}</span>
</van-cell>
<van-cell :border="false" title="生日贺卡" @click="showHk(o)"
v-if="o.isSend==1" is-link value="点击展示"></van-cell>
<van-cell :border="false" title="赠送礼物">
<van-image style="width: 50px;height: 50px" :src="'/file_server/fileStreamPreview?id=' + o.files[0].id" @click="showGift('/file_server/fileStreamPreview?id=' + o.files[0].id,o.id)"></van-image>
</van-cell>
</van-cell-group>
</div>
</div>
</van-list>
<van-empty
v-if="mLoading==false&&giftInfo.length==0"
class="custom-image"
image="/none.svg"
description="暂无数据"
></van-empty>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el:'#app',
mixins: [mobileMixins],
data(){
return{
show: false,
images:'',
giftInfo:[],
}
},
methods:{
doSearch(){
this.pageForm.pageNumber = 1
this.giftInfo = []
this.finished = false
this.onLoad()
},
async onLoad() {
this.mLoading = true
const res = await $.post('/mobile/birthday/greeting/giftInfo', this.pageForm)
this.mLoading = false
if (res.code === 0) {
res.data.list.forEach(v => {
if(!(typeof v.files =='object')){
v.files = JSON.parse(v.files)
}
})
this.giftInfo = this.giftInfo.concat(res.data.list)
if (this.giftInfo.length >= res.data.totalCount) {
this.finished = true
} else {
this.pageForm.pageNumber++
}
}
},
showGift(data){
vant.ImagePreview([data])
},
showHk(o){
window.location.href='/mobile/birthday/greeting/hk?id='+o.id
},
},
created(){
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,173 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
.sub{
width: 100px;
height: 100px;
background-size: 100% 100%;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
</style>
<div id="app" style="background-color: white">
<van-nav-bar title="精选礼物" left-arrow placeholder fixed
@click-left="history.back()"></van-nav-bar>
<div>
<span style="color: rgb(114,90,69);margin-left: 10px;font-size: 14px;font-weight: bold">正在为
<span v-for="o in tableData"> {{o.username}} </span>挑选礼物
</span>
</div>
<div>
<van-grid :column-num="6" :border="false">
<van-grid-item
v-for="o in tableData"
:key="o.userId" style="padding: 5px;">
<div style="background-color: rgb(203,200,222);
width: 50px;
height: 50px;
background-size: 100% 100%;
border-radius: 25px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
">
<span style="font-size: 20px">{{o.username.substr(0,1)}}</span>
</div>
<span style="font-size: 10px">{{o.username}}</span>
</van-grid-item>
</van-grid>
</div>
<div>
<div><h3 style="margin-left: 10px">精选礼物</h3></div>
<div>
<van-grid :column-num="3" :border="false">
<van-grid-item
v-for="(o,index) in listGift"
:key="o.id" style="padding: 5px;">
<div class="sub">
<van-image :src="'/file_server/fileStreamPreview?id=' + o.files[0].id" @click="sendGift('/file_server/fileStreamPreview?id=' + o.files[0].id,o.id)"></van-image>
</div>
<span style="font-size: 10px">{{o.giftName}}</span>
</van-grid-item>
</van-grid>
</div>
</div>
<div>
<van-dialog v-model="show" title="送上礼物" show-cancel-button :before-close="beforeClose">
<div>
<img :src="images" style="display: flex;align-items: center;justify-content: center;height: 100px;margin: 0 auto"/>
</div>
<div style="margin-top: 10px;margin-bottom: 10px">
<van-field
v-model="form.message"
rows="2"
autosize
label="祝福语"
type="textarea"
maxlength="100"
placeholder="请输入祝福语"
show-word-limit
></van-field>
</div>
<div style="margin-top: 10px;margin-bottom: 10px">
<van-row>
<van-col span="16">
<van-cell center title="是否发送贺卡">
<template #right-icon>
<van-switch v-model="form.isSend" @click="send" size="24"></van-switch>
</template>
</van-cell>
</van-col>
<van-col span="8">
<van-button round type="info" @click="showHk()">贺卡展示</van-button>
</van-col>
</van-row>
</div>
</van-dialog>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el:'#app',
data(){
return{
pageForm:{
greetingResult:localStorage.getItem("greetingResult"),
},
tableData:[],
listGift:[],
show: false,
images:'',
form:{
giftId:'',
message:'',
isSend:false,
},
}
},
methods:{
async loadList(){
const res = await $.post('/mobile/birthday/greeting/loadList',this.pageForm)
if (res.code === 0) {
this.tableData = res.data
}
},
async giftList(){
const res = await $.get('/mobile/birthday/greeting/giftList')
if (res.code === 0) {
res.data.forEach(v => {
if(!(typeof v.files =='object')){
v.files = JSON.parse(v.files)
}
})
this.listGift = res.data
}
},
sendGift(data,id){
this.show=true;
this.images=data;
this.form.giftId=id;
this.form.greetingResult=this.pageForm.greetingResult;
},
beforeClose : async function (action, done) { // 点击事件 - 备注按钮提示框
if (action === 'confirm') { // 确认
const res = await $.post('/mobile/birthday/greeting/sendBlessing', this.form)
if (res.code === 0) {
done(); // 关闭提示框
this.$toast.success("赠送成功")
} else {
done();
this.$toast.fail("操作失败")
}
} else if (action === 'cancel') { // 取消
this.show = false;
this.images = '';
this.form = {};
done(); // 关闭提示框
}
},
send(){
console.log(this.form.isSend)
},
showHk(){
window.location.href='/mobile/birthday/greeting/hk'
}
},
created(){
this.loadList();
this.giftList();
}
});
</script>
<!--#
}
#-->
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff