移植杭医版生日祝福
This commit is contained in:
+7
-31
@@ -1,12 +1,8 @@
|
||||
package com.budwk.app.zhgh.dayofficework.birthdayWishes.task;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.budwk.app.sys.models.Sys_msg;
|
||||
import com.budwk.app.sys.services.SysMsgService;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.json.Json;
|
||||
@@ -15,16 +11,12 @@ import org.quartz.JobDataMap;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@IocBean
|
||||
@Slf4j
|
||||
public class BirthdayWishesTask implements Job {
|
||||
|
||||
@Inject
|
||||
protected SysMsgService sysMsgService;
|
||||
@Inject
|
||||
protected Dao dao;
|
||||
private UserBirthdayService userBirthdayService;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -34,28 +26,12 @@ public class BirthdayWishesTask implements Job {
|
||||
log.info("=================================参数:{}", Json.toJson(dataMap));
|
||||
|
||||
String planName = dataMap.getString("name");
|
||||
|
||||
String today = DateUtil.today();
|
||||
String title = planName + today;
|
||||
String content = dataMap.getString("template");
|
||||
|
||||
Sys_msg msg = new Sys_msg();
|
||||
msg.setTitle(planName + today);
|
||||
msg.setType("user");
|
||||
msg.setSendType("show");
|
||||
msg.setSendAt(DateUtil.current());
|
||||
msg.setNote(dataMap.getString("template"));
|
||||
|
||||
// 查询今天生日的会员用户
|
||||
Sql sql = Sqls.create("SELECT loginname FROM sys_user WHERE member = 1 AND MONTH(birthday) = MONTH(CURRENT_DATE) AND DAY(birthday) = DAY(CURRENT_DATE)");
|
||||
sql.setCallback(Sqls.callback.strList());
|
||||
dao.execute(sql);
|
||||
List<String> users = sql.getList(String.class);
|
||||
// 转为数组
|
||||
String[] userArray = users.toArray(String[]::new);
|
||||
log.info("=================================查询今天生日会员用户:{}", users);
|
||||
|
||||
if(userArray.length > 0){
|
||||
log.info("=================================发送消息:{}", msg);
|
||||
sysMsgService.saveMsg(msg, userArray, true);
|
||||
}
|
||||
// 统一由生日服务查询当天生日会员、生成移动端链接并记录系统推送日志。
|
||||
int receiverCount = userBirthdayService.sendTodayBirthdayMessages(title, content);
|
||||
log.info("=================================当天生日通知进入发送队列人数:{}", receiverCount);
|
||||
}
|
||||
}
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package com.budwk.app.zhgh.staffmanage.birthday.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.models.UserBirthdayConfig;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.param.UserBirthdayPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.param.UserBirthdaySendMsgForm;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
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;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/staffManage/birthday/manage")
|
||||
@Ok("json:full")
|
||||
@Api(tags = "生日祝福管理")
|
||||
public class UserBirthdayManageController {
|
||||
|
||||
@Inject
|
||||
private UserBirthdayService userBirthdayService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/birthday/manage/index.html")
|
||||
@SaCheckPermission("staff.birthday.manage")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@Ok("beetl:/platform/zhghh5/staffmanage/birthday/index.html")
|
||||
@SaCheckLogin
|
||||
public void h5(String id, HttpServletRequest request) {
|
||||
request.setAttribute("fileId", StrUtil.blankToDefault(id, ""));
|
||||
request.setAttribute("userName", SecurityUtil.getUserUsername());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询生日人员。
|
||||
*
|
||||
* @param pageForm 分页、姓名/工号、工会、单位、人员状态及生日日期条件
|
||||
* @return Result.data 为 Pagination,list 是人员数据,totalCount 是总数
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("分页查询生日人员")
|
||||
@SaCheckPermission("staff.birthday.manage")
|
||||
public Result pageData(UserBirthdayPageForm pageForm) {
|
||||
return Result.success(userBirthdayService.pageData(pageForm));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出生日人员 Excel。
|
||||
*
|
||||
* @param pageForm 与列表查询一致的筛选条件
|
||||
* @param response 返回 XSSF 格式的 xlsx 文件流
|
||||
*/
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("导出生日人员")
|
||||
@SaCheckPermission("staff.birthday.manage")
|
||||
public void doExport(UserBirthdayPageForm pageForm, HttpServletResponse response) {
|
||||
userBirthdayService.exportXlsx(pageForm, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按查询条件批量发送通知。
|
||||
* form.title 为标题,form.content 为正文,其余字段为人员筛选条件;返回 data 为接收人数。
|
||||
*/
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@ApiOperation("按查询条件发送生日通知")
|
||||
@SaCheckPermission("staff.birthday.manage")
|
||||
public Result sendMsgByQueryUsers(UserBirthdaySendMsgForm form) {
|
||||
Result validation = validateMessage(form);
|
||||
if (validation != null) {
|
||||
return validation;
|
||||
}
|
||||
int count = userBirthdayService.sendByQuery(form, SecurityUtil.getUserId(), SecurityUtil.getUserUsername());
|
||||
if (count == 0) {
|
||||
return Result.error("当前条件下没有可发送的人员");
|
||||
}
|
||||
return Result.success("已提交发送,共" + count + "人", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向单个用户发送通知。
|
||||
* form.userId 为接收人用户ID,title、content 为消息内容;返回 data 为发送人数 1。
|
||||
*/
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@ApiOperation("向单个用户发送生日通知")
|
||||
@SaCheckPermission("staff.birthday.manage")
|
||||
public Result sendMsgByUser(UserBirthdaySendMsgForm form) {
|
||||
Result validation = validateMessage(form);
|
||||
if (validation != null) {
|
||||
return validation;
|
||||
}
|
||||
if (StrUtil.isBlank(form.getUserId())) {
|
||||
return Result.error("未获取到消息接收人");
|
||||
}
|
||||
boolean sent = userBirthdayService.sendByUser(form, SecurityUtil.getUserId(), SecurityUtil.getUserUsername());
|
||||
return sent ? Result.success("已提交发送", 1) : Result.error("未获取到消息接收人");
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("获取生日页面配置")
|
||||
@SaCheckLogin
|
||||
public Result getConfig() {
|
||||
return Result.success(userBirthdayService.getConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存生日页面图片配置。
|
||||
* config.picUrl 为背景图片文件ID,config.birthdayUrl 为福利贺卡文件ID;返回保存后的配置对象。
|
||||
*/
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@ApiOperation("保存生日页面配置")
|
||||
@SaCheckPermission("staff.birthday.manage")
|
||||
public Result saveOrModifyConfig(UserBirthdayConfig config) {
|
||||
return Result.success("保存成功", userBirthdayService.saveConfig(config));
|
||||
}
|
||||
|
||||
private Result validateMessage(UserBirthdaySendMsgForm form) {
|
||||
if (form == null || StrUtil.isBlank(form.getTitle())) {
|
||||
return Result.error("请输入发送标题");
|
||||
}
|
||||
if (StrUtil.isBlank(form.getContent())) {
|
||||
return Result.error("请输入发送内容");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
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.param.UserBirthdayMsgLogPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayMsgLogService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
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("/platform/staffManage/birthday/msgLog")
|
||||
@Ok("json:full")
|
||||
@Api(tags = "生日消息记录")
|
||||
public class UserBirthdayMsgLogController {
|
||||
|
||||
@Inject
|
||||
private UserBirthdayMsgLogService userBirthdayMsgLogService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/birthday/msglog/index.html")
|
||||
@SaCheckPermission("staff.birthday.msgLog")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询生日消息记录。
|
||||
*
|
||||
* @param pageForm 分页、姓名/工号、工会和单位查询条件
|
||||
* @return Result.data 为 Pagination,list 是发送记录,totalCount 是总数
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("分页查询生日消息记录")
|
||||
@SaCheckPermission("staff.birthday.msgLog")
|
||||
public Result pageData(UserBirthdayMsgLogPageForm pageForm) {
|
||||
return Result.success(userBirthdayMsgLogService.pageData(pageForm));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.budwk.app.zhgh.staffmanage.birthday.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.ColDefine;
|
||||
import org.nutz.dao.entity.annotation.ColType;
|
||||
import org.nutz.dao.entity.annotation.Column;
|
||||
import org.nutz.dao.entity.annotation.Comment;
|
||||
import org.nutz.dao.entity.annotation.Name;
|
||||
import org.nutz.dao.entity.annotation.Table;
|
||||
import org.nutz.dao.entity.annotation.TableMeta;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
/**
|
||||
* 生日祝福页面配置。
|
||||
* picUrl、birthdayUrl 保存 sys_file 文件 ID,由移动端按文件 ID 获取图片。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("user_birthday_config")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("生日祝福页面配置")
|
||||
public class UserBirthdayConfig extends BaseModel {
|
||||
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@PrevInsert(uu32 = true)
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("移动端背景图片文件ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String picUrl;
|
||||
|
||||
@Column
|
||||
@Comment("生日福利贺卡图片文件ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String birthdayUrl;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.budwk.app.zhgh.staffmanage.birthday.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.ColDefine;
|
||||
import org.nutz.dao.entity.annotation.ColType;
|
||||
import org.nutz.dao.entity.annotation.Column;
|
||||
import org.nutz.dao.entity.annotation.Comment;
|
||||
import org.nutz.dao.entity.annotation.Name;
|
||||
import org.nutz.dao.entity.annotation.Table;
|
||||
import org.nutz.dao.entity.annotation.TableMeta;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
/**
|
||||
* 生日祝福消息发送记录。
|
||||
* 每个接收人保存一条记录,便于按人员、单位和工会追溯发送情况。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("user_birthday_msg_log")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("生日祝福消息发送记录")
|
||||
public class UserBirthdayMsgLog extends BaseModel {
|
||||
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@PrevInsert(uu32 = true)
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("推送时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String pushTime;
|
||||
|
||||
@Column
|
||||
@Comment("消息标题")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 255)
|
||||
private String msgTitle;
|
||||
|
||||
@Column
|
||||
@Comment("消息内容")
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
private String msgContent;
|
||||
|
||||
@Column
|
||||
@Comment("接收人用户ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String receiveBy;
|
||||
|
||||
@Column
|
||||
@Comment("接收人姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String receiveName;
|
||||
|
||||
@Column
|
||||
@Comment("推送人用户ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String pushBy;
|
||||
|
||||
@Column
|
||||
@Comment("推送人姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
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;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.budwk.app.zhgh.staffmanage.birthday.param;
|
||||
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberInfoPageForm;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 生日消息记录分页查询参数,复用人员模块的工会、单位及数据范围条件。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("生日消息记录分页查询参数")
|
||||
public class UserBirthdayMsgLogPageForm extends MemberInfoPageForm {
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.budwk.app.zhgh.staffmanage.birthday.param;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberInfoPageForm;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
|
||||
/**
|
||||
* 生日人员分页查询参数。
|
||||
* startDate、endDate 接收 yyyy-MM-dd;生日比较只使用月日,允许查询跨年区间。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("生日人员分页查询参数")
|
||||
public class UserBirthdayPageForm extends MemberInfoPageForm {
|
||||
|
||||
@ApiModelProperty("生日开始日期,格式 yyyy-MM-dd")
|
||||
private String startDate;
|
||||
|
||||
@ApiModelProperty("生日结束日期,格式 yyyy-MM-dd")
|
||||
private String endDate;
|
||||
|
||||
/**
|
||||
* 将生日日期条件追加到已有人员查询条件。
|
||||
* 未传日期时默认查询今天起十五天内的生日,跨年时自动使用 OR 条件。
|
||||
*
|
||||
* @param cnd 已包含人员范围和会员状态的查询条件
|
||||
*/
|
||||
public void buildBirthdaySearch(Cnd cnd) {
|
||||
String start = normalizeMonthDay(startDate);
|
||||
String end = normalizeMonthDay(endDate);
|
||||
if (StrUtil.isAllBlank(start, end)) {
|
||||
start = DateUtil.format(DateUtil.date(), "MM-dd");
|
||||
end = DateUtil.format(DateUtil.offsetDay(DateUtil.date(), 15), "MM-dd");
|
||||
}
|
||||
|
||||
if (StrUtil.isAllNotBlank(start, end)) {
|
||||
if (start.compareTo(end) > 0) {
|
||||
cnd.and(new Static(String.format(
|
||||
"(DATE_FORMAT(u.birthday, '%%m-%%d') >= '%s' OR DATE_FORMAT(u.birthday, '%%m-%%d') <= '%s')",
|
||||
start, end)));
|
||||
} else {
|
||||
cnd.and(new Static(String.format(
|
||||
"DATE_FORMAT(u.birthday, '%%m-%%d') >= '%s' AND DATE_FORMAT(u.birthday, '%%m-%%d') <= '%s'",
|
||||
start, end)));
|
||||
}
|
||||
} else if (StrUtil.isNotBlank(start)) {
|
||||
cnd.and(new Static(String.format("DATE_FORMAT(u.birthday, '%%m-%%d') >= '%s'", start)));
|
||||
} else {
|
||||
cnd.and(new Static(String.format("DATE_FORMAT(u.birthday, '%%m-%%d') <= '%s'", end)));
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeMonthDay(String value) {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
return null;
|
||||
}
|
||||
return DateUtil.format(DateUtil.parse(value), "MM-dd");
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.budwk.app.zhgh.staffmanage.birthday.param;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 生日祝福发送参数。
|
||||
* 查询条件用于批量确定接收人;userId 用于单人发送;title、content 为消息标题和正文。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("生日祝福发送参数")
|
||||
public class UserBirthdaySendMsgForm extends UserBirthdayPageForm {
|
||||
|
||||
@ApiModelProperty("消息标题")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty("消息正文")
|
||||
private String content;
|
||||
|
||||
@ApiModelProperty("单人发送时的用户ID")
|
||||
private String userId;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
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.models.UserBirthdayMsgLog;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.param.UserBirthdayMsgLogPageForm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 生日祝福消息记录服务。
|
||||
*/
|
||||
public interface UserBirthdayMsgLogService extends BaseService<UserBirthdayMsgLog> {
|
||||
|
||||
/**
|
||||
* 查询生日消息记录。
|
||||
*
|
||||
* @param pageForm 人员、工会、单位及分页条件
|
||||
* @return 当前页发送记录及总数
|
||||
*/
|
||||
Pagination pageData(UserBirthdayMsgLogPageForm pageForm);
|
||||
|
||||
/**
|
||||
* 为每个接收人写入一条发送记录。
|
||||
*
|
||||
* @param loginNames 接收人工号集合
|
||||
* @param title 消息标题
|
||||
* @param content 消息正文
|
||||
* @param link 移动端生日祝福完整链接
|
||||
* @param pushBy 推送人用户ID
|
||||
* @param pushByName 推送人姓名
|
||||
* @param pushType 手动推送或系统推送
|
||||
*/
|
||||
void insertLogs(List<String> loginNames, String title, String content, String link,
|
||||
String pushBy, String pushByName, String pushType);
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
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.models.UserBirthdayConfig;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.param.UserBirthdayPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.param.UserBirthdaySendMsgForm;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 生日祝福业务服务。
|
||||
*/
|
||||
public interface UserBirthdayService extends BaseService<Sys_user> {
|
||||
|
||||
/**
|
||||
* 分页查询生日会员。
|
||||
*
|
||||
* @param pageForm 人员、组织和生日日期查询条件
|
||||
* @return 当前页人员列表及总数
|
||||
*/
|
||||
Pagination pageData(UserBirthdayPageForm pageForm);
|
||||
|
||||
/**
|
||||
* 导出符合查询条件的生日会员。
|
||||
*
|
||||
* @param pageForm 查询条件
|
||||
* @param response Excel 文件响应
|
||||
*/
|
||||
void exportXlsx(UserBirthdayPageForm pageForm, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 按当前查询条件批量发送生日通知。
|
||||
*
|
||||
* @param form 查询条件、标题和正文
|
||||
* @param pushBy 推送人用户ID
|
||||
* @param pushByName 推送人姓名
|
||||
* @return 实际进入发送队列的接收人数
|
||||
*/
|
||||
int sendByQuery(UserBirthdaySendMsgForm form, String pushBy, String pushByName);
|
||||
|
||||
/**
|
||||
* 向指定用户发送生日通知。
|
||||
*
|
||||
* @param form userId、标题和正文
|
||||
* @param pushBy 推送人用户ID
|
||||
* @param pushByName 推送人姓名
|
||||
* @return 是否找到接收人并进入发送队列
|
||||
*/
|
||||
boolean sendByUser(UserBirthdaySendMsgForm form, String pushBy, String pushByName);
|
||||
|
||||
/**
|
||||
* 向当天生日的会员发送系统生日通知,供 Quartz 任务调用。
|
||||
*
|
||||
* @param title 消息标题
|
||||
* @param content 消息正文
|
||||
* @return 实际进入发送队列的接收人数
|
||||
*/
|
||||
int sendTodayBirthdayMessages(String title, String content);
|
||||
|
||||
/**
|
||||
* 获取最新生日页面配置;未配置时返回空配置对象。
|
||||
*/
|
||||
UserBirthdayConfig getConfig();
|
||||
|
||||
/**
|
||||
* 新增或更新生日页面配置。
|
||||
*
|
||||
* @param config 背景图片和福利贺卡文件ID
|
||||
* @return 保存后的配置
|
||||
*/
|
||||
UserBirthdayConfig saveConfig(UserBirthdayConfig config);
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package com.budwk.app.zhgh.staffmanage.birthday.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.models.UserBirthdayMsgLog;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.param.UserBirthdayMsgLogPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayMsgLogService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
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.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Lang;
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pagination pageData(UserBirthdayMsgLogPageForm pageForm) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
log.*,
|
||||
u.loginname AS loginname,
|
||||
u.username AS username,
|
||||
u.unitName AS unitName,
|
||||
u.unionName AS unionName
|
||||
FROM user_birthday_msg_log log
|
||||
LEFT JOIN vw_user u ON u.id = log.receiveBy
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
pageForm.buildSearch(cnd, "u.");
|
||||
cnd.desc("log.pushTime");
|
||||
sql.setCondition(cnd);
|
||||
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void insertLogs(List<String> loginNames, String title, String content, String link,
|
||||
String pushBy, String pushByName, String pushType) {
|
||||
if (Lang.isEmpty(loginNames)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 只根据当前批次的工号读取接收人,日志中的用户ID和姓名保持发送当时快照。
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT id, loginname, username
|
||||
FROM vw_user
|
||||
$condition
|
||||
""");
|
||||
sql.setCondition(Cnd.where("loginname", "in", loginNames));
|
||||
List<NutMap> users = listMap(sql);
|
||||
String pushTime = DateUtil.now();
|
||||
List<UserBirthdayMsgLog> logs = users.stream().map(user -> {
|
||||
UserBirthdayMsgLog log = new UserBirthdayMsgLog();
|
||||
log.setId(R.UU32());
|
||||
log.setReceiveBy(user.getString("id"));
|
||||
log.setReceiveName(user.getString("username"));
|
||||
log.setMsgTitle(title);
|
||||
log.setMsgContent(content);
|
||||
log.setPushBy(pushBy);
|
||||
log.setPushByName(pushByName);
|
||||
log.setPushType(pushType);
|
||||
log.setPushTime(pushTime);
|
||||
log.setLink(link);
|
||||
return log;
|
||||
}).toList();
|
||||
if (!logs.isEmpty()) {
|
||||
dao().fastInsert(logs);
|
||||
}
|
||||
}
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
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.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.base.Globals;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.models.UserBirthdayConfig;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.param.UserBirthdayPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.param.UserBirthdaySendMsgForm;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayMsgLogService;
|
||||
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
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.aop.Aop;
|
||||
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.List;
|
||||
|
||||
@Slf4j
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class UserBirthdayServiceImpl extends BaseServiceImpl<Sys_user> implements UserBirthdayService {
|
||||
|
||||
@Inject
|
||||
private SysMsgService sysMsgService;
|
||||
|
||||
@Inject
|
||||
private UserBirthdayMsgLogService userBirthdayMsgLogService;
|
||||
|
||||
public UserBirthdayServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pagination pageData(UserBirthdayPageForm pageForm) {
|
||||
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), buildBirthdaySql(pageForm));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportXlsx(UserBirthdayPageForm pageForm, HttpServletResponse response) {
|
||||
try {
|
||||
List<NutMap> list = listMap(buildBirthdaySql(pageForm));
|
||||
List<ExcelExportEntity> columns = new ArrayList<>();
|
||||
columns.add(new ExcelExportEntity("工号", "loginname", 20));
|
||||
columns.add(new ExcelExportEntity("姓名", "username", 20));
|
||||
columns.add(new ExcelExportEntity("性别", "sex", 10));
|
||||
columns.add(new ExcelExportEntity("联系方式", "mobile", 20));
|
||||
columns.add(new ExcelExportEntity("出生日期", "birthday", 20));
|
||||
columns.add(new ExcelExportEntity("生日倒计时(天)", "daysUntilBirthday", 20));
|
||||
columns.add(new ExcelExportEntity("在职状态", "userState", 20));
|
||||
columns.add(new ExcelExportEntity("人员类型", "personType", 20));
|
||||
columns.add(new ExcelExportEntity("人员性质", "preparedBy", 20));
|
||||
columns.add(new ExcelExportEntity("所属工会", "unionName", 25));
|
||||
columns.add(new ExcelExportEntity("所属单位", "unitName", 25));
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
try (Workbook workbook = ExcelExportUtil.exportExcel(exportParams, columns, list)) {
|
||||
CommonDownloadUtil.download("生日人员名单.xlsx", workbook, response);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("导出生日人员名单失败", e);
|
||||
throw new RuntimeException("导出生日人员名单失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public int sendByQuery(UserBirthdaySendMsgForm form, String pushBy, String pushByName) {
|
||||
List<String> loginNames = listMap(buildBirthdaySql(form)).stream()
|
||||
.map(user -> user.getString("loginname"))
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.distinct()
|
||||
.toList();
|
||||
return sendMessages(loginNames, form.getTitle(), form.getContent(), pushBy, pushByName, "手动推送");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public boolean sendByUser(UserBirthdaySendMsgForm form, String pushBy, String pushByName) {
|
||||
Sys_user user = dao().fetch(Sys_user.class, form.getUserId());
|
||||
if (user == null || StrUtil.isBlank(user.getLoginname())) {
|
||||
return false;
|
||||
}
|
||||
sendMessages(List.of(user.getLoginname()), form.getTitle(), form.getContent(),
|
||||
pushBy, pushByName, "手动推送");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public int sendTodayBirthdayMessages(String title, String content) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT loginname
|
||||
FROM vw_user
|
||||
WHERE member = 1
|
||||
AND birthday IS NOT NULL
|
||||
AND MONTH(birthday) = MONTH(CURRENT_DATE)
|
||||
AND DAY(birthday) = DAY(CURRENT_DATE)
|
||||
""");
|
||||
sql.setCallback(Sqls.callback.strList());
|
||||
dao().execute(sql);
|
||||
List<String> loginNames = sql.getList(String.class);
|
||||
return sendMessages(loginNames, title, content, "system", "系统推送", "系统推送");
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserBirthdayConfig getConfig() {
|
||||
UserBirthdayConfig config = dao().fetch(UserBirthdayConfig.class, Cnd.NEW().desc("updatedAt"));
|
||||
return config == null ? new UserBirthdayConfig() : config;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public UserBirthdayConfig saveConfig(UserBirthdayConfig config) {
|
||||
dao().insertOrUpdate(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建生日人员公共查询。
|
||||
* 查询参数包含姓名/工号、组织、人员状态和生日区间;返回字段供列表、导出和批量发送共用。
|
||||
*/
|
||||
private Sql buildBirthdaySql(UserBirthdayPageForm pageForm) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
u.id AS id,
|
||||
u.loginname AS loginname,
|
||||
u.username AS username,
|
||||
u.sex AS sex,
|
||||
u.birthday AS birthday,
|
||||
u.mobile AS mobile,
|
||||
u.unitName AS unitName,
|
||||
u.unionName AS unionName,
|
||||
u.userState AS userState,
|
||||
u.personType AS personType,
|
||||
u.preparedBy AS 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", "=", 1);
|
||||
pageForm.buildSearch(cnd, "u.");
|
||||
pageForm.buildBirthdaySearch(cnd);
|
||||
cnd.asc("daysUntilBirthday");
|
||||
sql.setCondition(cnd);
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将生日消息写入当前项目消息中心并生成发送记录。
|
||||
* Sys_msg.url 用于站内消息点击,正文中的完整链接供外部短信/微信渠道访问。
|
||||
*/
|
||||
private int sendMessages(List<String> loginNames, String title, String content,
|
||||
String pushBy, String pushByName, String pushType) {
|
||||
List<String> recipients = loginNames == null ? List.of() : loginNames.stream()
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (recipients.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
UserBirthdayConfig config = getConfig();
|
||||
String pagePath = "/platform/staffManage/birthday/manage/h5";
|
||||
if (StrUtil.isNotBlank(config.getBirthdayUrl())) {
|
||||
pagePath = pagePath + "?id=" + config.getBirthdayUrl();
|
||||
}
|
||||
String fullLink = Globals.AppDomain + pagePath;
|
||||
|
||||
Sys_msg message = new Sys_msg();
|
||||
message.setTitle(title);
|
||||
message.setNote(content + "\n" + fullLink);
|
||||
message.setUrl(pagePath);
|
||||
message.setType("user");
|
||||
message.setSendType("show");
|
||||
message.setSendAt(Times.getTS());
|
||||
message.setCreatedBy(pushBy);
|
||||
sysMsgService.saveMsg(message, recipients.toArray(String[]::new), true);
|
||||
|
||||
userBirthdayMsgLogService.insertLogs(recipients, title, content, fullLink,
|
||||
pushBy, pushByName, pushType);
|
||||
return recipients.size();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* H5 弹框浏览器历史栈管理器。
|
||||
* 页面只注册关闭回调;本文件全局统一监听 popstate,避免每个页面重复绑定返回事件。
|
||||
*/
|
||||
;((global) => {
|
||||
const callbacks = new Map()
|
||||
const popupStack = []
|
||||
let tokenSeed = 0
|
||||
let stateSeed = 0
|
||||
let pendingClear = null
|
||||
|
||||
const invokeClose = (entry) => {
|
||||
const callback = callbacks.get(entry.token)
|
||||
if (callback) callback(entry.popupKey)
|
||||
}
|
||||
|
||||
global.addEventListener("popstate", (event) => {
|
||||
if (pendingClear) {
|
||||
event.stopImmediatePropagation()
|
||||
const clear = pendingClear
|
||||
pendingClear = null
|
||||
clear.resolve()
|
||||
return
|
||||
}
|
||||
if (!popupStack.length) return
|
||||
|
||||
// 有弹框历史时消费本次返回事件,阻止 PJAX 将页面直接切回上一个业务页面。
|
||||
event.stopImmediatePropagation()
|
||||
const entry = popupStack.pop()
|
||||
invokeClose(entry)
|
||||
}, true)
|
||||
|
||||
global.h5PopupHistory = {
|
||||
register(closeCallback) {
|
||||
tokenSeed += 1
|
||||
const token = "h5-popup-page-" + tokenSeed
|
||||
callbacks.set(token, closeCallback)
|
||||
return token
|
||||
},
|
||||
unregister(token) {
|
||||
callbacks.delete(token)
|
||||
for (let index = popupStack.length - 1; index >= 0; index -= 1) {
|
||||
if (popupStack[index].token === token) popupStack.splice(index, 1)
|
||||
}
|
||||
},
|
||||
open(token, popupKey) {
|
||||
if (!callbacks.has(token)) return
|
||||
const current = popupStack[popupStack.length - 1]
|
||||
if (current && current.token === token && current.popupKey === popupKey) return
|
||||
|
||||
stateSeed += 1
|
||||
const entry = {token:token,popupKey:popupKey,stateId:stateSeed}
|
||||
popupStack.push(entry)
|
||||
const state = Object.assign({}, global.history.state || {}, {
|
||||
__h5PopupHistory: {token:token,popupKey:popupKey,stateId:stateSeed}
|
||||
})
|
||||
global.history.pushState(state, global.document.title, global.location.href)
|
||||
},
|
||||
close(token, popupKey) {
|
||||
const current = popupStack[popupStack.length - 1]
|
||||
if (!current || current.token !== token || current.popupKey !== popupKey) return false
|
||||
global.history.back()
|
||||
return true
|
||||
},
|
||||
clear(token) {
|
||||
const entries = popupStack.filter((entry) => entry.token === token)
|
||||
if (!entries.length) return Promise.resolve()
|
||||
|
||||
entries.forEach((entry) => invokeClose(entry))
|
||||
for (let index = popupStack.length - 1; index >= 0; index -= 1) {
|
||||
if (popupStack[index].token === token) popupStack.splice(index, 1)
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
pendingClear = {resolve:resolve}
|
||||
global.history.go(-entries.length)
|
||||
})
|
||||
}
|
||||
}
|
||||
})(window)
|
||||
File diff suppressed because one or more lines are too long
@@ -61,6 +61,7 @@
|
||||
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
|
||||
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
|
||||
<script src="${base!}/assets/platform/js/util/h5AutoShowError.js"></script>
|
||||
<script src="${base!}/assets/platform/js/util/h5PopupHistory.js"></script>
|
||||
|
||||
<!--富文本编辑器-->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/wangEditor4/wangEditor.css"/>
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<search @search="searchBirthday">
|
||||
<search-item label="姓名/工号">
|
||||
<el-input v-model="pageForm.searchKeyword" clearable placeholder="请输入姓名或工号"></el-input>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会"
|
||||
style="width:100%" @change="handleUnionChange" @clear="handleUnionChange">
|
||||
<el-option v-for="item in unions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="所属单位">
|
||||
<el-select v-model="pageForm.unitId" clearable filterable placeholder="请选择所属单位" style="width:100%">
|
||||
<el-option v-for="item in units" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="在职状态">
|
||||
<dict-select v-model="queryForm.userStates" code="USER_STATE" multiple placeholder="请选择在职状态"></dict-select>
|
||||
</search-item>
|
||||
<search-item label="人员类型">
|
||||
<dict-select v-model="queryForm.personTypes" code="USER_PERSON_TYPE" multiple placeholder="请选择人员类型"></dict-select>
|
||||
</search-item>
|
||||
<search-item label="人员性质">
|
||||
<dict-select v-model="queryForm.preparedBys" code="PREPARED_BY" multiple placeholder="请选择人员性质"></dict-select>
|
||||
</search-item>
|
||||
<search-item label="生日日期">
|
||||
<el-date-picker v-model="birthdayRange" type="daterange" value-format="yyyy-MM-dd"
|
||||
range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
|
||||
style="width:100%" @change="handleBirthdayRangeChange"></el-date-picker>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt10">
|
||||
<table-tool label="生日人员(默认展示未来15天,日期查询忽略年份)"
|
||||
:columns.sync="tableColumns">
|
||||
<el-button size="small" type="primary" @click="doExport">导出Excel</el-button>
|
||||
<el-button size="small" type="primary" @click="openSendByQuery">发送通知</el-button>
|
||||
<el-button size="small" type="primary" @click="openConfig">配置图片</el-button>
|
||||
</table-tool>
|
||||
<el-table ref="table" v-loading="tableLoading" :data="tableData" :size="tableSize"
|
||||
row-key="id" style="width:100%" @sort-change="pageOrder">
|
||||
<el-table-column :index="indexMethod" label="序号" type="index" width="70" align="center"></el-table-column>
|
||||
<el-table-column v-for="column in tableColumns" v-if="column.visible !== false"
|
||||
:key="column.prop" :prop="column.prop"
|
||||
:label="column.label" :width="column.width" :fixed="column.fixed"
|
||||
:sortable="column.sortable"
|
||||
header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="210" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" @click="openView(scope.row)">人员信息</el-button>
|
||||
<el-button size="mini" type="primary" @click="openSendByUser(scope.row)">发送通知</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<template #view>
|
||||
<member-info ref="memberInfoRef"></member-info>
|
||||
</template>
|
||||
</guava>
|
||||
|
||||
<el-dialog title="按查询条件发送生日通知" :visible.sync="sendQueryDialogVisible"
|
||||
:close-on-click-modal="false" width="50%" @close="closeSendByQuery">
|
||||
<el-form ref="sendQueryFormRef" :model="sendQueryForm" label-width="90px">
|
||||
<el-form-item label="发送标题" prop="title"
|
||||
:rules="[{required:true,message:'请输入发送标题',trigger:['blur','change']}]">
|
||||
<el-input v-model="sendQueryForm.title" maxlength="255" placeholder="请输入发送标题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="发送内容" prop="content"
|
||||
:rules="[{required:true,message:'请输入发送内容',trigger:['blur','change']}]">
|
||||
<el-input v-model="sendQueryForm.content" type="textarea" :rows="6" placeholder="请输入发送内容"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer">
|
||||
<el-button @click="closeSendByQuery">取消</el-button>
|
||||
<el-button type="primary" :loading="formLoading" @click="sendByQuery">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="向单个用户发送生日通知" :visible.sync="sendUserDialogVisible"
|
||||
:close-on-click-modal="false" width="50%" @close="closeSendByUser">
|
||||
<el-form ref="sendUserFormRef" :model="sendUserForm" label-width="90px">
|
||||
<el-form-item label="发送对象">
|
||||
<el-input v-model="sendUserForm.userInfo" readonly></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="发送标题" prop="title"
|
||||
:rules="[{required:true,message:'请输入发送标题',trigger:['blur','change']}]">
|
||||
<el-input v-model="sendUserForm.title" maxlength="255" placeholder="请输入发送标题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="发送内容" prop="content"
|
||||
:rules="[{required:true,message:'请输入发送内容',trigger:['blur','change']}]">
|
||||
<el-input v-model="sendUserForm.content" type="textarea" :rows="6" placeholder="请输入发送内容"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer">
|
||||
<el-button @click="closeSendByUser">取消</el-button>
|
||||
<el-button type="primary" :loading="formLoading" @click="sendByUser">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="生日祝福页面图片配置" :visible.sync="configDialogVisible"
|
||||
:close-on-click-modal="false" width="50%" @close="closeConfig">
|
||||
<el-form ref="configFormRef" :model="configForm" label-width="110px">
|
||||
<el-form-item label="页面背景图片" prop="picUrl">
|
||||
<file-upload :value.sync="configForm.picUrl" :upload_number="1" upload_mode="image"
|
||||
accept=".jpg,.jpeg,.png" upload_result_category="interval"></file-upload>
|
||||
<div class="text-muted">建议使用竖版生日主题图片。</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="福利贺卡图片" prop="birthdayUrl">
|
||||
<file-upload :value.sync="configForm.birthdayUrl" :upload_number="1" upload_mode="image"
|
||||
accept=".jpg,.jpeg,.png" upload_result_category="interval"></file-upload>
|
||||
<div class="text-muted">移动端点击“领取生日福利”后展示该图片。</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer">
|
||||
<el-button @click="closeConfig">取消</el-button>
|
||||
<el-button type="primary" :loading="formLoading" @click="saveConfig">保存</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("/platform/zhgh/staffmanage/member/common/info/memberInfo.js"){}#-->
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
searchKeyword: "",
|
||||
unionId: "",
|
||||
unitId: "",
|
||||
userStates: "[]",
|
||||
personTypes: "[]",
|
||||
preparedBys: "[]",
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "",
|
||||
pageOrderBy: ""
|
||||
},
|
||||
queryForm: {userStates:[],personTypes:[],preparedBys:[]},
|
||||
birthdayRange: [],
|
||||
unions: [],
|
||||
units: [],
|
||||
tableColumns: [
|
||||
{prop:"loginname",label:"工号",sortable:true,width:"120"},
|
||||
{prop:"username",label:"姓名",sortable:true,width:"110"},
|
||||
{prop:"sex",label:"性别",width:"70"},
|
||||
{prop:"mobile",label:"联系方式",width:"130"},
|
||||
{prop:"birthday",label:"出生日期",sortable:true,width:"120"},
|
||||
{prop:"daysUntilBirthday",label:"生日倒计时(天)",sortable:true,width:"150"},
|
||||
{prop:"userState",label:"在职状态",width:"100"},
|
||||
{prop:"personType",label:"人员类型",width:"130"},
|
||||
{prop:"preparedBy",label:"人员性质",width:"130"},
|
||||
{prop:"unionName",label:"所属工会",width:"180"},
|
||||
{prop:"unitName",label:"所属单位",width:"180"}
|
||||
],
|
||||
sendQueryDialogVisible: false,
|
||||
sendQueryForm: {title:"",content:""},
|
||||
sendUserDialogVisible: false,
|
||||
sendUserForm: {userId:"",userInfo:"",title:"",content:""},
|
||||
configDialogVisible: false,
|
||||
configForm: {id:"",picUrl:"",birthdayUrl:""}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"member-info": MEMBER_INFO
|
||||
},
|
||||
methods: {
|
||||
initOrganizationOptions() {
|
||||
if (this.$auth.hasRoleOr("SYSADMIN, SCHOOL_UNION_ADMIN, SCHOOL_UNION_MEMBER_ADMIN")) {
|
||||
this.$businessTool.listUnion().then((data) => {
|
||||
this.$set(this, "unions", data || [])
|
||||
})
|
||||
this.$businessTool.listUnit().then((data) => {
|
||||
this.$set(this, "units", data || [])
|
||||
})
|
||||
} else {
|
||||
const unionId = this.$store.state.user.union.id
|
||||
this.$businessTool.listUnion(unionId).then((data) => {
|
||||
this.$set(this, "unions", data || [])
|
||||
})
|
||||
this.$businessTool.listUnit(unionId).then((data) => {
|
||||
this.$set(this, "units", data || [])
|
||||
})
|
||||
}
|
||||
},
|
||||
handleUnionChange() {
|
||||
this.$set(this.pageForm, "unitId", "")
|
||||
this.$set(this, "units", [])
|
||||
if (this.pageForm.unionId) {
|
||||
this.$businessTool.listUnit(this.pageForm.unionId).then((data) => {
|
||||
this.$set(this, "units", data || [])
|
||||
})
|
||||
}
|
||||
},
|
||||
handleBirthdayRangeChange(value) {
|
||||
this.$set(this.pageForm, "startDate", value && value.length ? value[0] : "")
|
||||
this.$set(this.pageForm, "endDate", value && value.length ? value[1] : "")
|
||||
this.searchBirthday()
|
||||
},
|
||||
searchBirthday() {
|
||||
this.$set(this.pageForm, "userStates", JSON.stringify(this.queryForm.userStates || []))
|
||||
this.$set(this.pageForm, "personTypes", JSON.stringify(this.queryForm.personTypes || []))
|
||||
this.$set(this.pageForm, "preparedBys", JSON.stringify(this.queryForm.preparedBys || []))
|
||||
this.doSearch()
|
||||
},
|
||||
requestParams() {
|
||||
return clone(this.pageForm)
|
||||
},
|
||||
doExport() {
|
||||
this.$downLoad(loc() + "/doExport", this.requestParams())
|
||||
},
|
||||
openView(row) {
|
||||
this.$refs.guava.view()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.memberInfoRef.onOpen(row.id)
|
||||
})
|
||||
},
|
||||
openSendByQuery() {
|
||||
this.$set(this, "sendQueryForm", Object.assign(this.requestParams(), {title:"",content:""}))
|
||||
this.$set(this, "sendQueryDialogVisible", true)
|
||||
},
|
||||
closeSendByQuery() {
|
||||
this.$set(this, "sendQueryDialogVisible", false)
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.sendQueryFormRef) this.$refs.sendQueryFormRef.clearValidate()
|
||||
})
|
||||
},
|
||||
sendByQuery() {
|
||||
this.$refs.sendQueryFormRef.validate((valid) => {
|
||||
if (!valid) return
|
||||
this.$confirm("确定按当前查询条件发送生日通知吗?", "提示", {type:"warning"}).then(() => {
|
||||
this.$set(this, "formLoading", true)
|
||||
this.$axios.post(loc() + "/sendMsgByQueryUsers", this.sendQueryForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.closeSendByQuery()
|
||||
} else {
|
||||
this.$message.warning(res.msg)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.$set(this, "formLoading", false)
|
||||
})
|
||||
}).catch(() => {})
|
||||
})
|
||||
},
|
||||
openSendByUser(row) {
|
||||
this.$set(this, "sendUserForm", {userId:row.id,userInfo:row.username + "(" + row.loginname + ")",title:"",content:""})
|
||||
this.$set(this, "sendUserDialogVisible", true)
|
||||
},
|
||||
closeSendByUser() {
|
||||
this.$set(this, "sendUserDialogVisible", false)
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.sendUserFormRef) this.$refs.sendUserFormRef.clearValidate()
|
||||
})
|
||||
},
|
||||
sendByUser() {
|
||||
this.$refs.sendUserFormRef.validate((valid) => {
|
||||
if (!valid) return
|
||||
this.$confirm("确定向" + this.sendUserForm.userInfo + "发送生日通知吗?", "提示", {type:"warning"}).then(() => {
|
||||
this.$set(this, "formLoading", true)
|
||||
this.$axios.post(loc() + "/sendMsgByUser", this.sendUserForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.closeSendByUser()
|
||||
} else {
|
||||
this.$message.warning(res.msg)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.$set(this, "formLoading", false)
|
||||
})
|
||||
}).catch(() => {})
|
||||
})
|
||||
},
|
||||
openConfig() {
|
||||
this.$set(this, "formLoading", true)
|
||||
this.$axios.post(loc() + "/getConfig").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$set(this, "configForm", Object.assign({id:"",picUrl:"",birthdayUrl:""}, res.data || {}))
|
||||
this.$set(this, "configDialogVisible", true)
|
||||
} else {
|
||||
this.$message.warning(res.msg)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.$set(this, "formLoading", false)
|
||||
})
|
||||
},
|
||||
closeConfig() {
|
||||
this.$set(this, "configDialogVisible", false)
|
||||
},
|
||||
saveConfig() {
|
||||
this.$confirm("确定保存生日祝福页面配置吗?", "提示", {type:"warning"}).then(() => {
|
||||
this.$set(this, "formLoading", true)
|
||||
this.$axios.post(loc() + "/saveOrModifyConfig", this.configForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.$set(this, "configForm", res.data)
|
||||
this.closeConfig()
|
||||
} else {
|
||||
this.$message.warning(res.msg)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.$set(this, "formLoading", false)
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initOrganizationOptions()
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,112 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="姓名/工号">
|
||||
<el-input v-model="pageForm.searchKeyword" clearable placeholder="请输入姓名或工号"></el-input>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会"
|
||||
style="width:100%" @change="handleUnionChange" @clear="handleUnionChange">
|
||||
<el-option v-for="item in unions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="所属单位">
|
||||
<el-select v-model="pageForm.unitId" clearable filterable placeholder="请选择所属单位" style="width:100%">
|
||||
<el-option v-for="item in units" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mt10">
|
||||
<table-tool label="生日消息记录" :columns.sync="tableColumns"></table-tool>
|
||||
<el-table ref="table" v-loading="tableLoading" :data="tableData" :size="tableSize"
|
||||
row-key="id" style="width:100%" @sort-change="pageOrder">
|
||||
<el-table-column :index="indexMethod" label="序号" type="index" width="70" align="center"></el-table-column>
|
||||
<el-table-column v-for="column in tableColumns" v-if="column.visible !== false"
|
||||
:key="column.prop" :prop="column.prop"
|
||||
:label="column.label" :width="column.width" :fixed="column.fixed"
|
||||
:sortable="column.sortable"
|
||||
header-align="center" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</template>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
searchKeyword: "",
|
||||
unionId: "",
|
||||
unitId: "",
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "",
|
||||
pageOrderBy: ""
|
||||
},
|
||||
unions: [],
|
||||
units: [],
|
||||
tableColumns: [
|
||||
{prop:"loginname",label:"工号",sortable:true,width:"120"},
|
||||
{prop:"username",label:"姓名",sortable:true,width:"110"},
|
||||
{prop:"unionName",label:"所属工会",width:"180"},
|
||||
{prop:"unitName",label:"所属单位",width:"180"},
|
||||
{prop:"msgTitle",label:"消息标题",width:"220"},
|
||||
{prop:"msgContent",label:"消息内容",width:"260"},
|
||||
{prop:"pushByName",label:"推送人",width:"120"},
|
||||
{prop:"pushTime",label:"推送时间",sortable:true,width:"170"},
|
||||
{prop:"pushType",label:"推送类型"}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initOrganizationOptions() {
|
||||
if (this.$auth.hasRoleOr("SYSADMIN, SCHOOL_UNION_ADMIN, SCHOOL_UNION_MEMBER_ADMIN")) {
|
||||
this.$businessTool.listUnion().then((data) => {
|
||||
this.$set(this, "unions", data || [])
|
||||
})
|
||||
this.$businessTool.listUnit().then((data) => {
|
||||
this.$set(this, "units", data || [])
|
||||
})
|
||||
} else {
|
||||
const unionId = this.$store.state.user.union.id
|
||||
this.$businessTool.listUnion(unionId).then((data) => {
|
||||
this.$set(this, "unions", data || [])
|
||||
})
|
||||
this.$businessTool.listUnit(unionId).then((data) => {
|
||||
this.$set(this, "units", data || [])
|
||||
})
|
||||
}
|
||||
},
|
||||
handleUnionChange() {
|
||||
this.$set(this.pageForm, "unitId", "")
|
||||
this.$set(this, "units", [])
|
||||
if (this.pageForm.unionId) {
|
||||
this.$businessTool.listUnit(this.pageForm.unionId).then((data) => {
|
||||
this.$set(this, "units", data || [])
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initOrganizationOptions()
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,311 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style scoped>
|
||||
html, body, #app { margin: 0; width: 100%; height: 100%; overflow: hidden; }
|
||||
.birthday-page { position: relative; width: 100%; height: 100vh; overflow: hidden; color: #7f1d1d; background: #e76f45; }
|
||||
.birthday-background { position: absolute; inset: 0; background-position: center; background-size: cover; z-index: 0; }
|
||||
.birthday-mask { position: absolute; inset: 0; z-index: 1; background: linear-gradient(180deg, rgba(255,255,255,.02), rgba(92,20,8,.16)); }
|
||||
.brand { position: absolute; top: calc(12px + env(safe-area-inset-top)); left: 14px; z-index: 8; padding: 7px 12px;
|
||||
border: 1px solid rgba(255,255,255,.68); border-radius: 16px; color: #fff; background: rgba(128,29,16,.2);
|
||||
font-size: 13px; font-weight: 600; backdrop-filter: blur(6px); }
|
||||
.member-tag { position: absolute; top: calc(12px + env(safe-area-inset-top)); right: 14px; z-index: 8; padding: 7px 12px;
|
||||
border-radius: 16px; color: #8a321c; background: rgba(255,244,213,.9); font-size: 12px; font-weight: 600; }
|
||||
.animation-zone { position: relative; z-index: 3; height: 52%; padding-top: calc(42px + env(safe-area-inset-top)); }
|
||||
.birthday-font { position: absolute; top: 8%; left: 4%; width: 92%; height: 47%; }
|
||||
.birthday-cake { position: absolute; left: 12%; bottom: -7%; width: 76%; height: 64%; }
|
||||
.confetti { position: absolute; top: -12px; width: 7px; height: 12px; border-radius: 3px; opacity: 0;
|
||||
animation: confetti-fall linear infinite; }
|
||||
.confetti:nth-child(3n) { background: #ffe16b; }
|
||||
.confetti:nth-child(3n+1) { background: #fff4dd; }
|
||||
.confetti:nth-child(3n+2) { background: #e9473f; }
|
||||
@keyframes confetti-fall { 0% { transform: translate3d(0,-20px,0) rotate(0); opacity: 0; }
|
||||
12% { opacity: 1; } 100% { transform: translate3d(35px,62vh,0) rotate(540deg); opacity: 0; } }
|
||||
.greeting-zone { position: relative; z-index: 4; height: 48%; display: flex; flex-direction: column; align-items: center;
|
||||
justify-content: flex-start; padding: 0 24px calc(20px + env(safe-area-inset-bottom)); box-sizing: border-box; }
|
||||
.greeting-card { width: 100%; box-sizing: border-box; padding: 20px 20px 16px; border: 1px solid rgba(164,60,26,.3);
|
||||
border-radius: 18px; background: rgba(255,249,228,.92); box-shadow: 0 12px 30px rgba(109,29,10,.22);
|
||||
text-align: center; animation: card-enter .7s ease-out both; }
|
||||
@keyframes card-enter { from { opacity: 0; transform: translateY(28px); } to { opacity: 1; transform: translateY(0); } }
|
||||
.greeting-title { margin-bottom: 10px; font-size: 18px; font-weight: 700; text-align: left; }
|
||||
.greeting-content { font-size: 15px; line-height: 1.8; letter-spacing: 1px; }
|
||||
.receive-button { width: 190px; margin-top: 18px; padding: 13px 20px; border: 0; border-radius: 25px; color: #fff;
|
||||
background: linear-gradient(135deg, #d9362b, #9d1717); box-shadow: 0 8px 18px rgba(131,21,17,.34);
|
||||
font-size: 16px; font-weight: 700; letter-spacing: 2px; }
|
||||
.music-button { position: absolute; right: 14px; bottom: calc(14px + env(safe-area-inset-bottom)); z-index: 9;
|
||||
width: 42px; height: 42px; border: 1px solid rgba(255,255,255,.7); border-radius: 50%; color: #fff;
|
||||
background: rgba(121,27,15,.42); font-size: 20px; }
|
||||
.music-button.playing { animation: music-rotate 3s linear infinite; }
|
||||
@keyframes music-rotate { to { transform: rotate(360deg); } }
|
||||
.music-consent-modal { position: fixed; inset: 0; z-index: 10040; display: flex; align-items: center; justify-content: center;
|
||||
padding: calc(24px + env(safe-area-inset-top)) 24px calc(24px + env(safe-area-inset-bottom)); box-sizing: border-box;
|
||||
background: rgba(61,13,8,.76); backdrop-filter: blur(7px); }
|
||||
.music-consent-card { position: relative; width: 100%; max-width: 390px; padding: 70px 24px 26px; box-sizing: border-box;
|
||||
border: 1px solid rgba(211,148,57,.72); border-radius: 28px; background: #fff8e8;
|
||||
box-shadow: 0 22px 60px rgba(56,8,4,.48), inset 0 0 0 5px rgba(255,255,255,.44); text-align: center;
|
||||
animation: music-dialog-enter .48s cubic-bezier(.2,.85,.32,1.16) both; }
|
||||
@keyframes music-dialog-enter { from { opacity: 0; transform: translateY(24px) scale(.92); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); } }
|
||||
.music-consent-emblem { position: absolute; top: -45px; left: 50%; display: flex; align-items: center; justify-content: center;
|
||||
width: 90px; height: 90px; border: 6px solid #fff8e8; border-radius: 50%; color: #fff;
|
||||
background: #d9362b; box-shadow: 0 10px 24px rgba(144,29,20,.34); transform: translateX(-50%); }
|
||||
.music-consent-emblem .fa { font-size: 36px; }
|
||||
.music-consent-title { margin: 0; color: #a51f1a; font-size: 31px; line-height: 1.25; font-weight: 800;
|
||||
letter-spacing: 2px; }
|
||||
.music-consent-subtitle { margin: 14px 0 0; color: #8b4b2c; font-size: 16px; line-height: 1.7; }
|
||||
.music-consent-divider { width: 70%; height: 1px; margin: 20px auto 18px; background: #e7b35b; opacity: .72; }
|
||||
.music-consent-tip { margin-bottom: 18px; color: #9a5d3a; font-size: 13px; line-height: 1.6; }
|
||||
.music-consent-button { display: flex; align-items: center; justify-content: center; width: 100%; height: 50px;
|
||||
border-radius: 25px; font-size: 17px; font-weight: 700; letter-spacing: 1px; outline: none; }
|
||||
.music-consent-button .fa { margin-right: 9px; font-size: 18px; }
|
||||
.music-consent-button.primary { border: 0; color: #fff; background: #c92921;
|
||||
box-shadow: 0 9px 20px rgba(174,35,27,.3); }
|
||||
.music-consent-button.secondary { margin-top: 13px; border: 1px solid #d99a45; color: #9b542b; background: #fffaf0; }
|
||||
.music-consent-button:active { transform: scale(.98); }
|
||||
.music-consent-button:focus-visible, .music-button:focus-visible { outline: 3px solid rgba(255,225,107,.9); outline-offset: 3px; }
|
||||
.coupon-modal { position: fixed; inset: 0; z-index: 10020; display: flex; align-items: center; justify-content: center;
|
||||
padding: calc(22px + env(safe-area-inset-top)) 20px calc(22px + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box; background: rgba(43,10,7,.72); backdrop-filter: blur(5px); }
|
||||
.coupon-panel { position: relative; width: 100%; max-width: 430px; max-height: 88vh; padding: 12px; box-sizing: border-box;
|
||||
border-radius: 20px; background: #fff7e6; box-shadow: 0 18px 55px rgba(40,5,0,.45); overflow: auto; }
|
||||
.coupon-close { position: absolute; top: 18px; right: 18px; z-index: 2; width: 34px; height: 34px; border: 0;
|
||||
border-radius: 50%; color: #fff; background: rgba(87,17,13,.72); font-size: 24px; line-height: 34px; }
|
||||
.coupon-image { display: block; width: 100%; border-radius: 14px; }
|
||||
.coupon-empty { min-height: 390px; padding: 70px 28px 30px; box-sizing: border-box; border: 2px dashed #d59569;
|
||||
border-radius: 14px; text-align: center; background: linear-gradient(155deg, #fff7df, #ffd9bd); }
|
||||
.coupon-empty-icon { font-size: 74px; }
|
||||
.coupon-empty-title { margin-top: 18px; color: #a21e19; font-size: 28px; font-weight: 800; }
|
||||
.coupon-empty-text { margin-top: 24px; color: #7b3a2a; font-size: 15px; line-height: 1.8; }
|
||||
.coupon-tip { padding: 10px 4px 2px; color: #7b3a2a; font-size: 12px; line-height: 1.6; text-align: center; }
|
||||
.loading-cover { position: fixed; inset: 0; z-index: 10030; display: flex; align-items: center; justify-content: center;
|
||||
color: #fff; background: rgba(76,20,10,.42); font-size: 14px; }
|
||||
@media (max-width: 375px) {
|
||||
.animation-zone { height: 48%; }
|
||||
.greeting-zone { height: 52%; padding-left: 18px; padding-right: 18px; }
|
||||
.greeting-card { padding: 16px; }
|
||||
.greeting-content { font-size: 14px; line-height: 1.65; }
|
||||
.receive-button { margin-top: 12px; }
|
||||
.music-consent-card { padding: 64px 20px 22px; }
|
||||
.music-consent-title { font-size: 27px; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.music-consent-card, .greeting-card, .confetti, .music-button.playing { animation: none; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app">
|
||||
<div class="birthday-page">
|
||||
<div class="birthday-background" :style="{backgroundImage:'url(' + config.backgroundUrl + ')'}"></div>
|
||||
<div class="birthday-mask"></div>
|
||||
<div class="brand">{{ appName }}</div>
|
||||
<div class="member-tag">会员专享</div>
|
||||
|
||||
<div class="animation-zone">
|
||||
<div ref="fontAnimation" class="birthday-font"></div>
|
||||
<div ref="cakeAnimation" class="birthday-cake"></div>
|
||||
<i v-for="item in confettiItems" :key="item.id" class="confetti" :style="item.style"></i>
|
||||
</div>
|
||||
|
||||
<div class="greeting-zone">
|
||||
<div class="greeting-card">
|
||||
<div class="greeting-title">亲爱的 {{ userName }} 老师:</div>
|
||||
<div class="greeting-content">
|
||||
步履所至,皆有回响,心之所向,岁月成诗。<br>
|
||||
在这个特别的日子里,校工会祝您:<br>
|
||||
生日快乐,幸福安康!
|
||||
</div>
|
||||
</div>
|
||||
<button class="receive-button" @click="openCoupon">领取生日福利</button>
|
||||
</div>
|
||||
|
||||
<button type="button" class="music-button" :class="{playing:musicPlaying}"
|
||||
:aria-label="musicPlaying ? '暂停音乐' : '播放音乐'" @click="toggleMusic">
|
||||
<i class="fa fa-music" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<div v-if="showMusicPrompt" class="music-consent-modal">
|
||||
<section class="music-consent-card" role="dialog" aria-modal="true" aria-labelledby="birthdayMusicTitle">
|
||||
<div class="music-consent-emblem" aria-hidden="true">
|
||||
<i class="fa fa-music"></i>
|
||||
</div>
|
||||
<h2 id="birthdayMusicTitle" class="music-consent-title">祝您生日快乐</h2>
|
||||
<p class="music-consent-subtitle">愿美好与祝福伴随您的每一天</p>
|
||||
<div class="music-consent-divider"></div>
|
||||
<div class="music-consent-tip">开启音乐,沉浸感受生日祝福</div>
|
||||
<button type="button" class="music-consent-button primary" @click="enableBirthdayMusic">
|
||||
<i class="fa fa-music" aria-hidden="true"></i>
|
||||
开启音乐
|
||||
</button>
|
||||
<button type="button" class="music-consent-button secondary" @click="declineBirthdayMusic">暂不开启</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div v-if="showCoupon" class="coupon-modal" @click.self="closeCoupon">
|
||||
<div class="coupon-panel">
|
||||
<button class="coupon-close" @click="closeCoupon">×</button>
|
||||
<img v-if="config.couponImageUrl" class="coupon-image" :src="config.couponImageUrl" alt="生日福利贺卡">
|
||||
<div v-else class="coupon-empty">
|
||||
<div class="coupon-empty-icon">🎂</div>
|
||||
<div class="coupon-empty-title">生日快乐</div>
|
||||
<div class="coupon-empty-text">校工会为您送上诚挚祝福。<br>福利贺卡尚未配置,请联系工作人员。</div>
|
||||
</div>
|
||||
<div class="coupon-tip">请按福利贺卡上的说明领取和使用生日福利</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="pageLoading" class="loading-cover">生日祝福加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="${base!}/assets/platform/plugins/lottie/lottie.min.js"></script>
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
appName: "${AppName!}",
|
||||
userName: "${userName!}",
|
||||
pageLoading: false,
|
||||
showMusicPrompt: false,
|
||||
musicPromptResolved: false,
|
||||
showCoupon: false,
|
||||
musicPlaying: false,
|
||||
popupHistoryToken: "",
|
||||
fontAnimation: null,
|
||||
cakeAnimation: null,
|
||||
audio: null,
|
||||
config: {
|
||||
backgroundUrl: "${base!}/assets/mobile/img/birthday/custom-bg.png",
|
||||
couponImageUrl: "",
|
||||
musicUrl: "${base!}/assets/mobile/music/birthday/birthdaySong.mp3",
|
||||
fontAnimationUrl: "${base!}/assets/mobile/lottie/birthday/happyBirthday.json",
|
||||
cakeAnimationUrl: "${base!}/assets/mobile/lottie/birthday/lihua.json"
|
||||
},
|
||||
confettiItems: Array.from({length:28}, (value, index) => ({
|
||||
id:index,
|
||||
style:{
|
||||
left:(index * 37 % 100) + "%",
|
||||
animationDelay:((index * 13 % 30) / 10) + "s",
|
||||
animationDuration:(3 + (index % 5) * .45) + "s"
|
||||
}
|
||||
}))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resolveFileUrl(value) {
|
||||
if (!value) return ""
|
||||
if (value.indexOf("http://") === 0 || value.indexOf("https://") === 0 || value.indexOf("/") === 0) return value
|
||||
return "/platform/sys/file/download?id=" + encodeURIComponent(value)
|
||||
},
|
||||
loadConfig() {
|
||||
this.$set(this, "pageLoading", true)
|
||||
this.$axios.post("/platform/staffManage/birthday/manage/getConfig").then((res) => {
|
||||
if (res.code !== 0 || !res.data) return
|
||||
if (res.data.picUrl) this.$set(this.config, "backgroundUrl", this.resolveFileUrl(res.data.picUrl))
|
||||
const linkFileId = "${fileId!}"
|
||||
const couponFile = linkFileId || res.data.birthdayUrl
|
||||
this.$set(this.config, "couponImageUrl", this.resolveFileUrl(couponFile))
|
||||
}).finally(() => {
|
||||
this.$set(this, "pageLoading", false)
|
||||
})
|
||||
},
|
||||
loadAnimations() {
|
||||
if (window.lottie && this.$refs.fontAnimation) {
|
||||
this.$set(this, "fontAnimation", window.lottie.loadAnimation({
|
||||
container:this.$refs.fontAnimation,
|
||||
renderer:"svg",
|
||||
loop:true,
|
||||
autoplay:true,
|
||||
path:this.config.fontAnimationUrl
|
||||
}))
|
||||
}
|
||||
if (window.lottie && this.$refs.cakeAnimation) {
|
||||
this.$set(this, "cakeAnimation", window.lottie.loadAnimation({
|
||||
container:this.$refs.cakeAnimation,
|
||||
renderer:"svg",
|
||||
loop:true,
|
||||
autoplay:true,
|
||||
path:this.config.cakeAnimationUrl
|
||||
}))
|
||||
}
|
||||
},
|
||||
openCoupon() {
|
||||
if (this.showCoupon) return
|
||||
this.$set(this, "showCoupon", true)
|
||||
window.h5PopupHistory.open(this.popupHistoryToken, "birthdayCoupon")
|
||||
},
|
||||
closeCoupon() {
|
||||
const historyHandled = window.h5PopupHistory.close(this.popupHistoryToken, "birthdayCoupon")
|
||||
if (!historyHandled) this.closeCouponState()
|
||||
},
|
||||
closeCouponState() {
|
||||
this.$set(this, "showCoupon", false)
|
||||
},
|
||||
openMusicPrompt() {
|
||||
if (this.showMusicPrompt || this.musicPromptResolved) return
|
||||
this.$set(this, "showMusicPrompt", true)
|
||||
window.h5PopupHistory.open(this.popupHistoryToken, "birthdayMusicPrompt")
|
||||
},
|
||||
closeMusicPrompt() {
|
||||
if (!this.showMusicPrompt) return
|
||||
// 先同步关闭界面,再回退弹框历史,避免部分 WebView 延迟 popstate 导致弹窗无法关闭。
|
||||
this.closeMusicPromptState()
|
||||
window.h5PopupHistory.close(this.popupHistoryToken, "birthdayMusicPrompt")
|
||||
},
|
||||
closeMusicPromptState() {
|
||||
this.$set(this, "showMusicPrompt", false)
|
||||
this.$set(this, "musicPromptResolved", true)
|
||||
},
|
||||
enableBirthdayMusic() {
|
||||
if (!this.showMusicPrompt) return
|
||||
this.closeMusicPrompt()
|
||||
this.playMusic()
|
||||
},
|
||||
declineBirthdayMusic() {
|
||||
if (!this.showMusicPrompt) return
|
||||
this.closeMusicPrompt()
|
||||
},
|
||||
playMusic() {
|
||||
if (!this.audio) this.$set(this, "audio", new Audio(this.config.musicUrl))
|
||||
this.audio.loop = true
|
||||
this.audio.volume = .3
|
||||
this.audio.play().then(() => {
|
||||
this.$set(this, "musicPlaying", true)
|
||||
}).catch(() => {
|
||||
this.$set(this, "musicPlaying", false)
|
||||
})
|
||||
},
|
||||
toggleMusic() {
|
||||
if (this.musicPlaying && this.audio) {
|
||||
this.audio.pause()
|
||||
this.$set(this, "musicPlaying", false)
|
||||
return
|
||||
}
|
||||
this.playMusic()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$set(this, "popupHistoryToken", window.h5PopupHistory.register((popupKey) => {
|
||||
if (popupKey === "birthdayCoupon") this.closeCouponState()
|
||||
if (popupKey === "birthdayMusicPrompt") this.closeMusicPromptState()
|
||||
}))
|
||||
this.loadConfig()
|
||||
this.loadAnimations()
|
||||
this.$nextTick(() => {
|
||||
this.openMusicPrompt()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.h5PopupHistory.unregister(this.popupHistoryToken)
|
||||
if (this.fontAnimation) this.fontAnimation.destroy()
|
||||
if (this.cakeAnimation) this.cakeAnimation.destroy()
|
||||
if (this.audio) {
|
||||
this.audio.pause()
|
||||
this.audio.src = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
Reference in New Issue
Block a user