This commit is contained in:
2026-02-26 13:47:12 +08:00
parent 4408f423df
commit 3817dc8eb4
50 changed files with 3447 additions and 735 deletions
@@ -67,4 +67,16 @@ public class Html2Text extends HTMLEditorKit.ParserCallback {
String result = StringEscapeUtils.unescapeHtml(plainText.trim());
return result;
}
public static String normalizeHtml(String html, double fontSizePt, double lineHeight) {
if (html == null || html.isEmpty()) return "";
String cleaned = html
.replaceAll("font-size\\s*:[^;]+;?", "")
.replaceAll("line-height\\s*:[^;]+;?", "");
// 🔑 10.5pt = 五号字
return String.format(
"<div style=\"font-size:%.1fpt;font-family:宋体;line-height:%.2f !important\">%s</div>",
fontSizePt, lineHeight, cleaned
);
}
}
+14 -4
View File
@@ -10,6 +10,7 @@ import com.google.gson.JsonObject;
import io.v.nutz.base.enums.Env;
import io.v.nutz.web.commons.base.Globals;
import io.v.nutz.web.commons.slog.annotation.SLog;
import io.v.nutz.zhgh.msgNotify.service.MsgNotifyService;
import io.v.nutz.zhgh.staffmanage.birthday.model.UserBirthdayMsgLog;
import io.v.nutz.zhgh.staffmanage.birthday.service.UserBirthdayMsgLogService;
import lombok.extern.slf4j.Slf4j;
@@ -54,6 +55,8 @@ public class MsgApi {
private RedisService redisService;
@Inject
private UserBirthdayMsgLogService userBirthdayMsgLogService;
@Inject
private MsgNotifyService msgNotifyService;
/**
@@ -78,7 +81,7 @@ public class MsgApi {
.filter(s -> !s.isEmpty())
.collect(Collectors.toList());
sendMsgInternal(channels, loginNamesList, mtype, title, content, imageUrl, link, null);
sendMsgInternal(channels, loginNamesList, mtype, title, content, imageUrl, link, null,null);
}
/**
@@ -93,20 +96,25 @@ public class MsgApi {
*/
@SLog(type = "api", tag = "消息发送", msg = "推送消息", param = true, result = true)
public void sendMsg(List<String> channels, List<String> loginNameList, Integer mtype, String title, String content, String imageUrl, String link) {
sendMsgInternal(channels, loginNameList, mtype, title, content, imageUrl, link, null);
sendMsgInternal(channels, loginNameList, mtype, title, content, imageUrl, link, "other",null);
}
@SLog(type = "api", tag = "消息发送", msg = "推送消息", param = true, result = true)
public void sendMsgInsertLog(List<String> channels, List<String> loginNameList, Integer mtype, String title, String content, String imageUrl, String link,String apiModule) {
sendMsgInternal(channels, loginNameList, mtype, title, content, imageUrl, link, "api",apiModule);
}
@SLog(type = "api", tag = "消息发送", msg = "推送消息", param = true, result = true)
public void sendMsgByBirthday(List<String> channels, List<String> loginNameList, Integer mtype, String title, String content, String imageUrl, String link) {
sendMsgInternal(channels, loginNameList, mtype, title, content, imageUrl, link, "birthday");
sendMsgInternal(channels, loginNameList, mtype, title, content, imageUrl, link, "birthday",null);
}
/**
* 核心私有方法:统一处理消息发送逻辑
*/
private void sendMsgInternal(List<String> channels, List<String> loginNameList, Integer mtype, String title, String content, String imageUrl, String link, String type) {
private void sendMsgInternal(List<String> channels, List<String> loginNameList, Integer mtype, String title, String content, String imageUrl, String link, String type,String apiModule) {
try {
if (!Globals.MyConfig.getBoolean("SendMsg")) {
log.info("发送失败,消息暂未开启!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
@@ -138,6 +146,8 @@ public class MsgApi {
if ("birthday".equals(type)) {
userBirthdayMsgLogService.insertLog(loginNameList, title, content, imageUrl, link);
} else if ("api".equals(type)) {
msgNotifyService.insertLog(loginNameList, title, content, link,apiModule);
}
} else {
@@ -1,9 +1,7 @@
package io.v.nutz.sys.controllers.platform.sys.club.applyIn;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.wizzer.framework.base.Result;
import cn.wizzer.framework.page.Pagination;
import io.v.nutz.base.annontation.ViReturn;
import io.v.nutz.base.service.BaseService;
import io.v.nutz.base.utils.DateUtil;
@@ -18,6 +16,7 @@ import io.v.nutz.sys.models.Sys_club_user;
import io.v.nutz.sys.models.Sys_user;
import io.v.nutz.sys.services.SysClubService;
import io.v.nutz.sys.services.SysClubUserService;
import io.v.nutz.web.commons.base.Globals;
import io.v.nutz.web.commons.utils.ShiroUtil;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
@@ -27,7 +26,6 @@ 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.sql.SqlCallback;
import org.nutz.dao.util.Daos;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.dao.util.cri.Static;
@@ -88,24 +86,24 @@ public class ClubApplyInController {
@Param(value = "pageOrderBy", required = false) String pageOrderBy,
@Param(value = "clubType", required = false) String clubType) {
Sql sql = Sqls.create("""
SELECT
club.*,
fzr.username fzrname,
unit.`name` fzr_unit_name,
(select yearAuditStatus from sys_club_user where clubid = club.id and userid = @userid and year(yearAudit) = @year and isNormal = true and yearAuditStatus is not null) as yearAuditStatus,
(select GROUP_CONCAT(username) from sys_user where id in (select userid from sys_club_user u where u.clubid=club.id and u.sf=1 and isNormal = true)) as hz,
(SELECT count( 1 ) FROM sys_club_user scu WHERE scu.clubid = club.id AND scu.`status` = 5 and isNormal = true ) cynum,
(SELECT count( 1 ) FROM sys_club_user scu WHERE scu.clubid = club.id AND scu.`status` in (1, 3) and isNormal = true) approvalnum,
(SELECT status FROM sys_club_user scu WHERE scu.clubid = club.id AND scu.userid = @userid and isNormal = true) status,
(SELECT invite FROM sys_club_user scu WHERE scu.clubid = club.id AND scu.userid = @userid and isNormal = true) invite,
(select count(*) from sys_user where userState='在职' and id in (select userid from sys_club_user where clubid = club.id and `status` = 5 and isNormal = true)) as worknum,
(select count(*) from sys_user where userState='退休' and id in (select userid from sys_club_user where clubid = club.id and `status` = 5 and isNormal = true)) as outnum
FROM
`sys_club` club
LEFT JOIN sys_user fzr ON club.concatPerson = fzr.id
LEFT JOIN sys_unit unit ON fzr.unitid = unit.id
$condition
""")
SELECT
club.*,
fzr.username fzrname,
unit.`name` fzr_unit_name,
(select yearAuditStatus from sys_club_user where clubid = club.id and userid = @userid and year(yearAudit) = @year and isNormal = true and yearAuditStatus is not null) as yearAuditStatus,
(select GROUP_CONCAT(username) from sys_user where id in (select userid from sys_club_user u where u.clubid=club.id and u.sf=1 and isNormal = true)) as hz,
(SELECT count( 1 ) FROM sys_club_user scu WHERE scu.clubid = club.id AND scu.`status` = 5 and isNormal = true ) cynum,
(SELECT count( 1 ) FROM sys_club_user scu WHERE scu.clubid = club.id AND scu.`status` in (1, 3) and isNormal = true) approvalnum,
(SELECT status FROM sys_club_user scu WHERE scu.clubid = club.id AND scu.userid = @userid and isNormal = true) status,
(SELECT invite FROM sys_club_user scu WHERE scu.clubid = club.id AND scu.userid = @userid and isNormal = true) invite,
(select count(*) from sys_user where userState='在职' and id in (select userid from sys_club_user where clubid = club.id and `status` = 5 and isNormal = true)) as worknum,
(select count(*) from sys_user where userState='退休' and id in (select userid from sys_club_user where clubid = club.id and `status` = 5 and isNormal = true)) as outnum
FROM
`sys_club` club
LEFT JOIN sys_user fzr ON club.concatPerson = fzr.id
LEFT JOIN sys_unit unit ON fzr.unitid = unit.id
$condition
""")
.setParam("userid", ShiroUtil.getPrincipalProperty("id"))
.setParam("year", cn.hutool.core.date.DateUtil.thisYear());
Cnd cnd = Cnd.NEW();
@@ -147,12 +145,12 @@ public class ClubApplyInController {
public Object validApply() {
//查询是否是会员
Sys_user sysUser = dao.fetch(Sys_user.class, ShiroUtil.getUserId());
if(sysUser.getMember() != 1) {
if (sysUser.getMember() != 1) {
return Result.error("您当前不是会员,无法申请");
}
int count = dao.count(Sys_club_user.class, Cnd.where("userid", "=", ShiroUtil.getUserId())
.and("isNormal", "=", true).and("`status`", "in", List.of(1, 3, 5)));
if(count >= 3) {
if (count >= 3) {
return Result.error("您已参与3个协会,无法申请");
}
return Result.success();
@@ -166,7 +164,7 @@ public class ClubApplyInController {
Sys_club sysClub = dao.fetch(Sys_club.class, sys_club_user.getClubid());
Sys_club_user sysClubUser = dao.fetch(Sys_club_user.class, Cnd.where("clubid", "=", sys_club_user.getClubid()).and("userid", "=", ShiroUtil.getUserId()));
if(sysClubUser == null) {
if (sysClubUser == null) {
sys_club_user.setInvite(false);
} else {
sys_club_user.setInvite(sysClubUser.getInvite());
@@ -185,7 +183,7 @@ public class ClubApplyInController {
sys_club_user.setGiveMoney(true);
Sys_club_user clubUser = sysClubUserService.insert(sys_club_user);
if(!isInvite && sys_club_user.getStatus() != 5) {
if (!isInvite && sys_club_user.getStatus() != 5) {
//插入后,发起待办流程
ClubUserApplyToDoHandler.START_PROCESS.exec(clubUser);
//发任务给协会会长
@@ -195,21 +193,21 @@ public class ClubApplyInController {
//根据协会id查询协会负责人,发短信
Sql sql = Sqls.create("""
SELECT
u.loginname as loginName,
u.username as userName,
u.unitid as unitId,
u.unitname as unitName,
u.mobile,
c.`name`
FROM
sys_user_role r
LEFT JOIN
`user` u ON r.userId = u.id
LEFT JOIN
sys_club c ON c.id = r.stid
$condition
""");
SELECT
u.loginname as loginName,
u.username as userName,
u.unitid as unitId,
u.unitname as unitName,
u.mobile,
c.`name`
FROM
sys_user_role r
LEFT JOIN
`user` u ON r.userId = u.id
LEFT JOIN
sys_club c ON c.id = r.stid
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("stid", "=", sys_club_user.getClubid());
cnd.and("roleId", "in", Lang.array(Roles.club01, Roles.club02, Roles.club03, Roles.club04));
@@ -219,9 +217,9 @@ public class ClubApplyInController {
String platformUsername = ShiroUtil.getPlatformUsername();
list.forEach(item -> {
String content = "%s老师您好,%s老师于%s申请加入%s请登陆智慧工会进行审核!".formatted(item.getString("userName")
, platformUsername, dateTime, item.getString("name"));
//msgApi.sendWxMsg(person, content);
String content = "%s老师于%s申请加入%s点击前往审核!".formatted(platformUsername, dateTime, item.getString("name"));
String link = Globals.AppDomain + "/platform/sys/club/sh/mobile";
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(item.getString("loginName")), 2, "协会入会", content, null, link, "协会管理");
});
}
return null;
@@ -279,9 +277,9 @@ public class ClubApplyInController {
String platformUsername = ShiroUtil.getPlatformUsername();
list.forEach(item -> {
String content = "%s老师您好,%s老师于%s撤销加入%s的申请!".formatted(item.getString("userName")
String content = "%s老师于%s撤销加入%s的申请!".formatted(item.getString("userName")
, platformUsername, DateUtil.getDateTime(), item.getString("name"));
//msgApi.sendWxMsg(person, content);
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(item.getString("loginName")), 1, "协会入会", content, null, null, "协会管理");
});
return null;
}
@@ -329,10 +327,10 @@ public class ClubApplyInController {
WHERE c.id = @id
""").setParam("id", id);
NutMap map = (NutMap) Daos.query(dao, sql.toString(), Sqls.callback.map());
if(StrUtil.isNotBlank(map.getString("clubAuditId"))) {
if (StrUtil.isNotBlank(map.getString("clubAuditId"))) {
map.put("clubAudit", dao.fetch(Audit.class, map.getString("clubAuditId")));
}
if(StrUtil.isNotBlank(map.getString("yearClubAuditId"))) {
if (StrUtil.isNotBlank(map.getString("yearClubAuditId"))) {
map.put("yearAudit", dao.fetch(Audit.class, map.getString("yearClubAuditId")));
}
return map;
@@ -343,7 +341,7 @@ public class ClubApplyInController {
@Aop(TransAop.READ_COMMITTED)
@RequiresPermissions("sys.club.sq")
public Object yearAudit(String id) {
if(StrUtil.isBlank(id)) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
dao.update(Sys_club_user.class, Chain.make("yearAudit", cn.hutool.core.date.DateUtil.now()).add("yearAuditStatus", 1),
@@ -216,7 +216,7 @@ public class ClubMyApplyController {
Sys_user sysUser = userMap.get(user);
String content = "%s老师您好,欢迎加入%s,请进入入会申请进行确认!".formatted(sysUser.getUsername(), sysClub.getName());
String link = Globals.AppDomain + "/platform/sys/club/sq/mobile";
msgApi.sendMsg(List.of("DingTalk"), user, 2, "协会入会邀请", content, "", link);
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(sysUser.getLoginname()), 2, "协会入会邀请", content, "", link,"协会管理");
dao.update(
Sys_club_user.class,
@@ -133,7 +133,7 @@ public class ClubXghReplyController {
if(sysClubUser != null) {
Sys_user sysUser = dao.fetch(Sys_user.class, sysClubUser.getUserid());
String content = "%s老师您好,您申请注册的%s已经审核通过。".formatted(sysUser.getUsername(), club.getName());
msgApi.sendMsg(List.of("DingTalk"), sysUser.getLoginname(), 1, "协会注册", content, "", "");
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(sysUser.getLoginname()), 1, "协会注册", content, "", "","协会管理");
}
}
club.setXghAuditId(audit.getId());
@@ -57,7 +57,7 @@ public class SourceChangeJob implements Job {
}).collect(Collectors.joining(","));
String content = "智慧工会" + today + "数据更新汇总:" + updateDataStr;
msgApi.sendMsg(List.of("DingTalk"), "2004000022", 1, "数据更新汇总", content, "", "");
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of("2004000022"), 1, "数据更新汇总", content, "", "","人员管理");
}
}
}
@@ -90,9 +90,7 @@ public class SendMsgBeforeClassJob implements Job {
item.getString("courseLocation")
);
}
msgApi.sendMsg(List.of("DingTalk"), item.getString("loginname"), 1, "活动开始通知", content, "", "");
// msgApi.sendWxMsg("温馨提醒","","text",content,List.of(item.getString("loginname")),"");
// sendMsg.pushWeChatMsg("温馨提醒", content, item.getString("loginname"));
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(item.getString("loginname")), 1, "活动开始通知", content, "", "","心理咨询");
});
}
@@ -0,0 +1,112 @@
package io.v.nutz.zhgh.mobile.suggestionBox.controller;
import cn.hutool.core.util.StrUtil;
import cn.wizzer.framework.base.Result;
import io.v.nutz.base.page.Pagination;
import io.v.nutz.base.query.PageForm;
import io.v.nutz.base.service.BaseService;
import io.v.nutz.web.commons.utils.ShiroUtil;
import io.v.nutz.zhgh.mobile.suggestionBox.models.SuggestionBox;
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.dao.util.cri.Static;
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 org.nutz.mvc.annotation.Param;
import java.time.LocalDateTime;
import java.util.Map;
@IocBean
@At("/platform/suggestionBox/admin")
@Ok("json:full")
public class SuggestionBoxAdminController {
@Inject
private Dao dao;
@Inject
private BaseService baseService;
@At("/h5")
@Ok("beetl:/mobile/suggestionBox/admin.html")
public void h5() {
}
/**
* 分页数据
*
* @param pageForm 分页
* @param status 状态 是否回复
* @param timeRange 时间范围 今日 本周 本月
* @return
*/
@At("/pageData")
public Result pageData(PageForm pageForm, Integer status, String timeRange, String keyword) {
Sql sql = Sqls.create("select * from suggestion_box $condition");
Cnd cnd = Cnd.NEW();
cnd.andEX("isReply", "=", status);
if (StrUtil.isNotBlank(timeRange)) {
switch (timeRange) {
case "today":
cnd.and(new Static("DATE(submitTime) = CURDATE()"));
break;
case "week":
cnd.and(new Static("WEEKOFYEAR(submitTime) = WEEKOFYEAR(CURDATE())"));
break;
case "month":
cnd.and(new Static("MONTH(submitTime) = MONTH(CURDATE())"));
break;
default:
break;
}
}
if (StrUtil.isNotBlank(keyword)) {
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.orLike("submitterName", keyword);
seg.orLike("content", keyword);
seg.orLike("title", keyword);
cnd.and(seg);
}
cnd.desc("submitTime");
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
}
/**
* 统计
*/
@At("/getStats")
public Result stats() {
// 查询总意见数
int total = dao.count(SuggestionBox.class);
// 待回复数
int pending = dao.count(SuggestionBox.class, Cnd.where("isReply", "=", 0));
// 已回复数
int replied = dao.count(SuggestionBox.class, Cnd.where("isReply", "=", 1));
// 今日新增
int today = dao.count(SuggestionBox.class, Cnd.NEW().and(new Static("DATE(submitTime) = CURDATE()")));
return Result.success(Map.of("total", total, "pending", pending, "replied", replied, "today", today));
}
/**
* 答复
*/
@At
public Result reply(@Param("reply") SuggestionBox suggestionBox) {
suggestionBox.setIsReply(true);
suggestionBox.setReplyTime(LocalDateTime.now());
suggestionBox.setReplyUserId(ShiroUtil.getUserId());
suggestionBox.setReplyUserName((String) ShiroUtil.getPrincipalProperty("username"));
dao.update(suggestionBox,"isReply|replyContent|replyTime|replyUserId|replyUserName");
return Result.success();
}
}
@@ -0,0 +1,84 @@
package io.v.nutz.zhgh.mobile.suggestionBox.controller;
import cn.wizzer.framework.base.Result;
import io.v.nutz.base.page.Pagination;
import io.v.nutz.base.query.PageForm;
import io.v.nutz.base.service.BaseService;
import io.v.nutz.base.utils.MsgApi;
import io.v.nutz.web.commons.base.Globals;
import io.v.nutz.web.commons.utils.ShiroUtil;
import io.v.nutz.zhgh.mobile.suggestionBox.models.SuggestionBox;
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.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.time.LocalDateTime;
import java.util.List;
@IocBean
@At("/platform/suggestionBox")
@Ok("json:full")
public class SuggestionBoxController {
@Inject
private Dao dao;
@Inject
private BaseService baseService;
@Inject
private MsgApi msgApi;
@At("/h5")
@Ok("beetl:/mobile/suggestionBox/index.html")
public void h5Index() {
}
@At("/h5/write")
@Ok("beetl:/mobile/suggestionBox/write.html")
public void h5Write() {
}
@At("/h5/mine")
@Ok("beetl:/mobile/suggestionBox/mine.html")
public void h5Mine() {
}
@At
public Result submit(@Param("suggestion") SuggestionBox suggestionBox) {
suggestionBox.setSubmitTime(LocalDateTime.now());
dao.insert(suggestionBox);
String link = Globals.AppDomain + "/platform/suggestionBox/admin/h5";
msgApi.sendMsgInsertLog(List.of("DingTalk"),List.of("2004000022"),2,"建言献策",ShiroUtil.getPlatformUsername()+"提交了一条建言献策待您回复","",link,"建言献策");
return Result.success();
}
@At
public Result delete(@Param("id") String id) {
dao.delete(SuggestionBox.class, id);
return Result.success();
}
/**
* 我的意见列表
*/
@At
public Result pageData(PageForm pageForm) {
Sql sql = Sqls.create("select * from suggestion_box $condition");
Cnd cnd = Cnd.NEW();
cnd.and("submitterId","=", ShiroUtil.getUserId());
cnd.desc("submitTime");
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
}
}
@@ -0,0 +1,116 @@
package io.v.nutz.zhgh.mobile.suggestionBox.models;
import cn.wizzer.framework.base.model.BaseModel;
import io.v.nutz.sys.models.Sys_file;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.time.LocalDateTime;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Table("suggestion_box")
@Comment("意见箱")
@Data
public class SuggestionBox extends BaseModel {
@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 submitterId;
@Column
@Comment("提交人姓名")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String submitterName;
@Column
@Comment("提交人工号")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String submitterLoginName;
@Column
@Comment("提交人单位ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String submitterUnitId;
@Column
@Comment("提交人单位名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String submitterUnitName;
@Column
@Comment("提交人分工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String submitterUnionId;
@Column
@Comment("提交人分工会名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String submitterUnionName;
@Column
@Comment("提交时间")
@ColDefine(type = ColType.DATETIME)
private LocalDateTime submitTime;
@Column
@Comment("意见名称")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String title;
@Column
@Comment("意见内容")
@ColDefine(type = ColType.TEXT)
private String content;
@Column
@Comment("附件")
@ColDefine(type = ColType.MYSQL_JSON)
private List<Sys_file> attachments;
@Column
@Comment("联系方式")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String concat;
@Column
@Comment("回复状态")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean isReply;
@Column
@Comment("回复内容")
@ColDefine(type = ColType.TEXT)
private String replyContent;
@Column
@Comment("回复时间")
@ColDefine(type = ColType.DATETIME)
private LocalDateTime replyTime;
@Column
@Comment("回复人ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String replyUserId;
@Column
@Comment("回复人姓名")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String replyUserName;
@Column
@Comment("回复附件")
@ColDefine(type = ColType.MYSQL_JSON)
private List<Sys_file> replyAttachments;
}
@@ -173,24 +173,23 @@ public class MsgNotifyController {
if (msgNotify.getSendMode().equals("one")) {
List<NutMap> list = msgNotifyService.getUserByRoleIds(msgNotify.getModule(), msgNotify.getTeacherMeetingId(), msgNotify.getRoleIds());
if (flag) {
msgNotifyService.sendMsg(msgNotify.getSendTypes(), list, msgNotify.getContent());
List<String> loginNames = list.stream().map(v -> v.getString("loginName")).toList();
msgNotifyService.sendMsg2(msgNotify.getSendTypes(), loginNames, msgNotify.getContent(),msgNotify.getUrl(),msgNotify.getTitle());
}
list.forEach(v -> {
if (flag) {
msgNotify.setSendTime(DateUtil.getDateTime());
}
msgNotifyUser.setUserId(v.getString("id"));
msgNotifyUser.setTitleId(msgNotify.getId());
dao.insert(msgNotifyUser);
});
dao.insert(msgNotifyUser);
} else if (msgNotify.getSendMode().equals("two")) {
List<NutMap> userScopes = msgNotifyService.getUserScopes(msgNotify.getActivityGroupId());
if (flag) {
List<String> loginNames = userScopes.stream().map(v -> v.getString("loginName")).toList();
msgNotifyService.sendMsg2(msgNotify.getSendTypes(), loginNames, msgNotify.getContent());
msgNotifyService.sendMsg2(msgNotify.getSendTypes(), loginNames, msgNotify.getContent(),msgNotify.getUrl(),msgNotify.getTitle());
}
userScopes.forEach(v -> {
if (flag) {
@@ -198,13 +197,13 @@ public class MsgNotifyController {
}
msgNotifyUser.setUserId(v.getString("id"));
msgNotifyUser.setTitleId(msgNotify.getId());
dao.insert(msgNotifyUser);
});
dao.insert(msgNotifyUser);
} else if (msgNotify.getSendMode().equals("three")) {
if (flag) {
List<User> user = dao.query(User.class, Cnd.where("id", "in", users));
List<String> loginNames = user.stream().map(User::getLoginname).toList();
msgNotifyService.sendMsg2(msgNotify.getSendTypes(), loginNames, msgNotify.getContent());
msgNotifyService.sendMsg2(msgNotify.getSendTypes(), loginNames, msgNotify.getContent(),msgNotify.getUrl(),msgNotify.getTitle());
}
for (String u : users) {
if (flag) {
@@ -212,23 +211,21 @@ public class MsgNotifyController {
}
msgNotifyUser.setUserId(u);
msgNotifyUser.setTitleId(msgNotify.getId());
dao.insert(msgNotifyUser);
}
dao.insert(msgNotifyUser);
} else if (msgNotify.getSendMode().equals("four")) {
if (StrUtil.isNotBlank(existsLoginNameRedisKey)) {
List<String> loginNames = redisService.lrange(existsLoginNameRedisKey, 0, -1);
List<User> userList = dao.query(User.class, Cnd.where("loginname", "in", loginNames));
List<NutMap> map = new ArrayList<>();
for (User u : userList) {
map.add(NutMap.NEW().addv("userId", u.getLoginname()).addv("mobile", u.getMobile()));
}
msgNotifyService.sendMsg(msgNotify.getSendTypes(), map, msgNotify.getContent());
msgNotifyService.sendMsg2(msgNotify.getSendTypes(), loginNames, msgNotify.getContent(),msgNotify.getUrl(),msgNotify.getTitle());
msgNotify.setSendTime(DateUtil.getDateTime());
for (User u : userList) {
msgNotifyUser.setUserId(u.getId());
msgNotifyUser.setTitleId(msgNotify.getId());
dao.insert(msgNotifyUser);
}
dao.insert(msgNotifyUser);
}
}
@@ -1,8 +1,10 @@
package io.v.nutz.zhgh.msgNotify;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import io.v.nutz.base.annontation.ViReturn;
import io.v.nutz.base.query.PageForm;
import io.v.nutz.base.utils.PageUtil;
import io.v.nutz.zhgh.msgNotify.models.MsgNotify;
import io.v.nutz.zhgh.msgNotify.models.MsgNotifyUser;
import io.v.nutz.base.service.ViService;
@@ -45,7 +47,7 @@ public class MsgNotifyListController {
@ViReturn
@RequiresPermissions("msgNotify.sendList")
public Object pageData(PageForm pageForm,
@Param(value = "titleId", required = false)String titleId,
@Param(value = "titleId", required = false) String titleId,
@Param(value = "dateRange", required = false) String[] dateRange,
@Param(value = "unitId", required = false) String unitId,
@Param(value = "unionId", required = false) String unionId,
@@ -59,15 +61,12 @@ public class MsgNotifyListController {
u.unionname,
u.unitname,
mnu.*,
mn.title,
mn.activityGroupId,
mn.module,
mn.teacherMeetingId,
mn.type,
mn.roleIds,
mn.sendTypes,
mn.content,
mn.sendTime,
mn.hold
FROM
`msg_notify_user` mnu
@@ -77,7 +76,7 @@ public class MsgNotifyListController {
""");
cnd.andEX("u.unionid", "=", unionId);
cnd.andEX("u.unitid", "=", unitId);
cnd.andEX("mn.hold", "=", 1);
cnd.and("mnu.sendTime", "is not", null);
cnd.andEX("mn.id", "=", titleId);
if (!ShiroUtil.hasRole("sysadmin")) {
cnd.and("mnu.userId", "=", ShiroUtil.getUserId());
@@ -96,8 +95,12 @@ public class MsgNotifyListController {
cnd.and("mn.sendTime", "<=", dateRange[1]);
}
cnd.desc("mn.sendTime");
cnd.desc("u.unitname");
if (Strings.isNotBlank(pageForm.getPageOrderName()) && Strings.isNotBlank(pageForm.getPageOrderBy())) {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
} else {
cnd.desc("mnu.sendTime").desc("u.unitname");
}
sql.setCondition(cnd);
return notifyViService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
@@ -28,6 +28,11 @@ public class MsgNotify {
@ColDefine(type = ColType.VARCHAR, width = 30)
private String title;
@Column
@Comment("发送Url")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String url;
@Column
@Comment("发送对象")
@ColDefine(type = ColType.VARCHAR, width = 32)
@@ -70,6 +75,11 @@ public class MsgNotify {
@ColDefine(type = ColType.VARCHAR, width = 1000)
private String content;
@Column
@Comment("发送URL")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String link;
@Column
@Comment("创建时间")
@ColDefine(type = ColType.VARCHAR, width = 1000)
@@ -80,6 +90,7 @@ public class MsgNotify {
@ColDefine(type = ColType.VARCHAR, width = 1000)
private String sendTime;
@Column
@Comment("是否保存")
@ColDefine(type = ColType.BOOLEAN)
@@ -36,11 +36,34 @@ public class MsgNotifyUser {
@ColDefine(type = ColType.VARCHAR, width = 50)
private String sendUser;
@Column
@Comment("标题id")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String titleId;
@Column
@Comment("标题")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String title;
@Column
@Comment("发送内容")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String content;
@Column
@Comment("发送URL")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String link;
@Column
@Comment("发送时间")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sendTime;
@Column
@Comment("发送模块")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String apiModule;
}
@@ -49,7 +49,7 @@ public interface MsgNotifyService {
*/
void sendMsg(List<String> sendTypes, List<NutMap> receivers, String content);
void sendMsg2(List<String> sendTypes, List<String> loginNames, String content);
void sendMsg2(List<String> sendTypes, List<String> loginNames, String content,String title,String url);
/**
@@ -73,4 +73,14 @@ public interface MsgNotifyService {
List<MsgNotifyUser> addMsgNotifyUser(MsgNotify msgNotify, String[] users);
/**
* 添加发送记录
* @param loginNameList
* @param title
* @param content
* @param link
*/
void insertLog (List<String> loginNameList, String title, String content, String link,String apiModule);
}
@@ -1,6 +1,7 @@
package io.v.nutz.zhgh.msgNotify.service.impl;
import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import io.v.nutz.base.service.impl.ViServiceImpl;
import io.v.nutz.base.utils.DateUtil;
@@ -148,11 +149,9 @@ public class MsgNotifyServiceImpl extends ViServiceImpl<MsgNotify> implements Ms
}
@Override
public void sendMsg2(List<String> sendTypes, List<String> loginNames, String content) {
public void sendMsg2(List<String> sendTypes, List<String> loginNames, String content, String title, String url) {
if (sendTypes.contains("DingTalk")) {
String url = URLUtil.encode("/file_server/fileStreamPreview?id=fa777e529921481c903c259115cc79e7");
String link = Globals.AppDomain + "/assets/platform/plugins/pdfJs/web/viewer.html?file="+url;
// msgApi.sendMsg(sendTypes, loginNames, 2, "祝贺您获得家情共话观影券,请查收观影指南!", "电影券已发放,请查收指南!",null ,link);
msgApi.sendMsg(sendTypes, loginNames, StrUtil.isNotBlank(url) ? 2 : 1, title, content, null, url);
}
}
@@ -269,5 +268,23 @@ public class MsgNotifyServiceImpl extends ViServiceImpl<MsgNotify> implements Ms
return msgNotifyUserList;
}
@Override
public void insertLog(List<String> loginNameList, String title, String content, String link,String apiModule) {
List<User> userList = dao().query(User.class, Cnd.where("loginname", "in", loginNameList));
List<MsgNotifyUser> msgNotifyUserList = new ArrayList<>();
for (User v : userList) {
MsgNotifyUser msgNotifyUser = new MsgNotifyUser();
msgNotifyUser.setSendUser(ShiroUtil.getUserId());
msgNotifyUser.setUserId(v.getId());
msgNotifyUser.setTitle(title);
msgNotifyUser.setLink(link);
msgNotifyUser.setContent(content);
msgNotifyUser.setSendTime(DateUtil.getDateTime());
msgNotifyUser.setApiModule(apiModule);
msgNotifyUserList.add(msgNotifyUser);
}
dao().insert(msgNotifyUserList);
}
}
@@ -331,11 +331,10 @@ public class ProposalBranchLeaderSuffixAuditController {
if(user == null) {
continue;
}
String loginname = user.getLoginname();
if(content.contains("****") && StrUtil.isBlank(proposalId)) {
content = content.replace("****", undertake.getUnitName());
}
msgApi.sendMsg(List.of("DingTalk"), loginname, 2, title, content, "", linkUrl);
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(user.getLoginname()), 2, title, content, "", linkUrl,"提案管理");
}
}
return Result.success();
@@ -148,13 +148,13 @@ public class ProposalFeedbackScoreController {
SqlExpressionGroup seg2 = new SqlExpressionGroup();
seg2.or("info.stateCode", ">=", ProposalState.FEEDBACKSCORE);
seg2.orGT("(select count(1) from proposal_reply pr where pr.proposalId = info.id and pr.undertakeType = 1)", 1);
seg2.orGT("(select count(1) from proposal_feedback pf where pf.proposalId = info.id = 1)", 1);
cnd.and(Cnd.exps(seg).or(seg2));
} else if (search.getIsAudit().equals("true")) {
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.or("info.stateCode", ">=", ProposalState.FEEDBACKSCORE);
seg.orGT("(select count(1) from proposal_reply pr where pr.proposalId = info.id and pr.undertakeType = 1)", 1);
seg.or("info.stateCode", ">", ProposalState.FEEDBACKSCORE);
seg.orGT("(select count(1) from proposal_feedback pf where pf.proposalId = info.id = 1)", 1);
cnd.and(seg);
} else if (search.getIsAudit().equals("false")) {
cnd.and("info.stateCode", "=", ProposalState.FEEDBACKSCORE);
@@ -444,9 +444,8 @@ public class ProposalFeedbackScoreController {
String linkUrl = Globals.AppDomain + "/platform/proposal/transact/feedback";
for (NutMap user : list) {
String loginname = user.getString("loginname");
System.out.println(JSONUtil.toJsonStr(user));
msgApi.sendMsg(List.of("DingTalk"), loginname, 2, title, content, "", linkUrl);
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(user.getString("loginname")), 2, title, content, "", linkUrl,"提案管理");
}
return Result.success();
} catch (Exception e) {
@@ -244,7 +244,7 @@ public class ProposalMineController {
String template = "{}代表,您好,{}代表的提案《{}》邀请您作为附议人,请您点击此消息或登录“智慧工会”系统进行附议,感谢您对教代会提案工作的大力支持!";
String content = StrUtil.format(template, user.getUsername(), sysUser.getUsername(), proposalInfo.getProposalName());
System.out.println(content);
msgApi.sendMsg(List.of("DingTalk"), user.getLoginname(), 2, "提案附议邀请", content, "", linkUrl);
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(user.getLoginname()), 2, "提案附议邀请", content, "", linkUrl,"提案管理");
}
ProposalToDoHandler.CREATE_SECONDED_TASK.exec(proposalId, NutMap.NEW().addv("secondedList", secondedList));
@@ -548,7 +548,7 @@ public class ProposalMineController {
String template = "{}代表,您好,您附议的提案《{}》已被{}代表(起草人)撤回重新修改,特此告知,烦请知悉。感谢您对教代会提案工作的大力支持!";
String content = StrUtil.format(template, map.getString("username"),
userMap.getString("proposalName"), userMap.getString("username"));
msgApi.sendMsg(List.of("DingTalk"), map.getString("loginname"), 1, "提案重新起草通知", content, "", "");
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(map.getString("loginname")), 1, "提案重新起草通知", content, "", "","提案管理");
}
}
@@ -557,7 +557,7 @@ public class ProposalMineController {
String template = "{}代表,您好,您附议的提案《{}》已被{}代表(起草人)撤回重新修改,您已无需附议。特此告知,烦请知悉。感谢您对教代会提案工作的大力支持!";
String content = StrUtil.format(template, map.getString("username"),
userMap.getString("proposalName"), userMap.getString("username"));
msgApi.sendMsg(List.of("DingTalk"), map.getString("loginname"), 1, "提案无需附议通知", content, "", "");
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(map.getString("loginname")), 1, "提案无需附议通知", content, "", "","提案管理");
}
}
@@ -245,7 +245,7 @@ public class ProposalReplyController {
for (NutMap reply : replyList) {
String loginname = reply.getString("loginname");
System.out.println(JSONUtil.toJsonStr(reply));
msgApi.sendMsg(List.of("DingTalk"), loginname, 2, title, content, "", linkUrl);
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(loginname), 2, title, content, "", linkUrl,"提案管理");
}
return Result.success();
} catch (Exception e) {
@@ -292,7 +292,7 @@ public class ProposalReplyController {
// 发送消息提醒
Sys_user proxyUser = dao.fetch(Sys_user.class, proxyUserId);
String msgContent = "{}您好!您有一条转交的提案待办理,请点击消息办理。";
msgApi.sendMsg(List.of("DingTalk"), proxyUser.getLoginname(), 2, "提案转交办理", StrUtil.format(msgContent, proxyUser.getUsername()), "", Globals.AppDomain + "/platform/proposal/transact/reply");
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(proxyUser.getLoginname()), 2, "提案转交办理", StrUtil.format(msgContent, proxyUser.getUsername()), "", Globals.AppDomain + "/platform/proposal/transact/reply","提案管理");
ProposalToDoHandler.CREATE_UNDERTAKE_PROXY_TASK.exec(proposalId, NutMap.NEW().addv("reply_forward", replyForward));
@@ -8,12 +8,15 @@ import com.deepoove.poi.policy.HackLoopTableRenderPolicy;
import io.v.nutz.base.service.impl.BaseServiceImpl;
import io.v.nutz.base.utils.Html2Text;
import io.v.nutz.base.utils.OfficeTemplateUtil;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.zhgh.proposal.services.ProposalExportService;
import io.v.nutz.zhgh.proposal.services.ProposalInfoService;
import io.v.nutz.sys.models.Sys_signature;
import io.v.nutz.web.commons.base.Globals;
import lombok.extern.slf4j.Slf4j;
import org.ddr.poi.html.HtmlRenderPolicy;
import org.ddr.poi.html.util.CSSLength;
import org.ddr.poi.html.util.CSSLengthUnit;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
@@ -26,6 +29,7 @@ import org.nutz.json.Json;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@@ -158,7 +162,7 @@ public class ProposalExportServiceImpl extends BaseServiceImpl implements Propos
LEFT JOIN sys_signature sign ON sign.id = pa.signId
LEFT JOIN sys_dict dict ON pa.other = dict.`code`
$condition
""");
""");
delegationCnd.where().andIn("pa.id", delegationAuditIds);
delegationAuditSql.setCondition(delegationCnd);
delegationAuditList = proposalInfoService.listMap(delegationAuditSql);
@@ -176,14 +180,14 @@ public class ProposalExportServiceImpl extends BaseServiceImpl implements Propos
List<NutMap> oneAuditRecordList = new ArrayList<>();
if (StrUtil.isNotBlank(nutMap.getString("caseAuditId"))) {
Sql oneAuditRecordSql = Sqls.create("""
SELECT
pa.*,
sign.`data` auditSign
FROM
proposal_audit pa
LEFT JOIN sys_signature sign ON sign.id = pa.signId
where pa.id in (@id)
""").setParam("id", Json.fromJsonAsList(String.class, nutMap.getString("caseAuditId")));
SELECT
pa.*,
sign.`data` auditSign
FROM
proposal_audit pa
LEFT JOIN sys_signature sign ON sign.id = pa.signId
where pa.id in (@id)
""").setParam("id", Json.fromJsonAsList(String.class, nutMap.getString("caseAuditId")));
oneAuditRecordList = proposalInfoService.listMap(oneAuditRecordSql);
if (Lang.isNotEmpty(oneAuditRecordList)) {
oneAuditRecordList.forEach(v -> {
@@ -218,7 +222,7 @@ public class ProposalExportServiceImpl extends BaseServiceImpl implements Propos
LEFT JOIN sys_signature sign ON sign.id = pa.signId
LEFT JOIN sys_dict dict ON pa.other = dict.`code`
WHERE pa.id in (@ids)
""").setParam("ids", branchCaseArray);
""").setParam("ids", branchCaseArray);
replyAuditList = proposalInfoService.listMap(replyAuditSql);
}
@@ -377,21 +381,31 @@ public class ProposalExportServiceImpl extends BaseServiceImpl implements Propos
if (Lang.isNotEmpty(feedbackList)) {
for (NutMap map : feedbackList) {
if (map.getString("feedbackResult").equals("满意")) {
map.setv("feedbackResult", "1.满意(√)2.较满意()3.不满意()");
map.setv("feedbackResult", "1.满意(√)2.较满意()3.基本满意()4.不满意()");
} else if (map.getString("feedbackResult").equals("比较满意")) {
map.setv("feedbackResult", "1.满意()2.较满意(√)3.不满意()");
map.setv("feedbackResult", "1.满意()2.较满意(√)3.基本满意()4.不满意()");
} else if (map.getString("feedbackResult").equals("基本满意")) {
map.setv("feedbackResult", "1.满意()2.比较满意()3.基本满意(√)4.不满意()");
} else {
map.setv("feedbackResult", "1.满意()2.较满意()3.不满意(√)");
map.setv("feedbackResult", "1.满意()2.较满意()3.基本满意()4.不满意(√)");
}
map.setv("feedbackOpinion", Html2Text.toPlainText(map.getString("feedbackOpinion")));
}
}
nutMap.setv("brief", Html2Text.normalizeHtml(nutMap.getString("brief"), 10.5, 1.2));
nutMap.setv("measures", Html2Text.normalizeHtml(nutMap.getString("measures"), 10.5, 1.2));
HackLoopTableRenderPolicy policy = new HackLoopTableRenderPolicy();
HtmlRenderPolicy htmlRenderPolicy = new HtmlRenderPolicy();
htmlRenderPolicy.getConfig().setShowDefaultTableBorderInTableCell(true);
htmlRenderPolicy.getConfig().setGlobalFontSize(new CSSLength(10.5, CSSLengthUnit.PT));
htmlRenderPolicy.getConfig().setGlobalFont("宋体");
Configure config = Configure.newBuilder().bind("secondedList", policy).bind("proposal.brief", htmlRenderPolicy).bind("proposal.measures", htmlRenderPolicy).build();
Configure poiConfig = Configure.newBuilder()
.bind("secondedList", policy)
.bind("proposal.brief", htmlRenderPolicy)
.bind("proposal.measures", htmlRenderPolicy)
.build();
HashMap<String, Object> map = new HashMap<>();
map.put("proposal", nutMap);
map.put("schoolName", Globals.MyConfig.getString("GxName"));
@@ -408,7 +422,7 @@ public class ProposalExportServiceImpl extends BaseServiceImpl implements Propos
String templateUrl = StrUtil.isBlank(nutMap.getString("hostUnitName")) ?
officeTemplateUtil.getPath("proposal_info") :
officeTemplateUtil.getPath("proposal_host_help_unit");
XWPFTemplate.compile(templateUrl, config).render(map).writeAndClose(outputStream);
XWPFTemplate.compile(templateUrl, poiConfig).render(map).writeAndClose(outputStream);
}
private PictureRenderData getImg(String base64) throws IOException {
@@ -416,4 +430,6 @@ public class ProposalExportServiceImpl extends BaseServiceImpl implements Propos
byte[] bytes = Base64.getDecoder().decode(s);
return new PictureRenderData(70, 30, ".png", bytes);
}
}
@@ -340,7 +340,7 @@ public class ProposalReplyServiceImpl extends ViServiceImpl<ProposalReply> imple
// 提醒一把手
List<String> underTakeLeader = proposalCommonService.getUnderTakeLeader(reply.getReplyUnitId());
String msgContent = "您转交的提案已由转交人办理完成,请您及时提交提案办理内容!";
msgApi.sendMsg(List.of("DingTalk"),underTakeLeader, 2, "转交答复完成", msgContent, "", Globals.AppDomain + "/platform/proposal/transact/reply");
msgApi.sendMsgInsertLog(List.of("DingTalk"),underTakeLeader, 2, "转交答复完成", msgContent, "", Globals.AppDomain + "/platform/proposal/transact/reply","提案管理");
return;
@@ -378,7 +378,7 @@ public class ProposalReplyServiceImpl extends ViServiceImpl<ProposalReply> imple
// 发送短信
List<String> underTakeSchoolLeader = proposalCommonService.getUnderTakeSchoolLeader(leaderSuffixAudit.getUnderTakeId());
String msgContent = "校领导,您分管部门承办的提案(建议)已提交办理结果,请您审核!";
msgApi.sendMsg(List.of("DingTalk"), underTakeSchoolLeader, 2, "提案校领导审批", msgContent, "", Globals.AppDomain + "/platform/proposal/transact/branchLeaderSuffixAudit");
msgApi.sendMsgInsertLog(List.of("DingTalk"), underTakeSchoolLeader, 2, "提案校领导审批", msgContent, "", Globals.AppDomain + "/platform/proposal/transact/branchLeaderSuffixAudit","提案管理");
} else {
proposalInfo.setStateCode(ProposalState.FINISH);
//流程完结
@@ -184,7 +184,7 @@ public class MemberApplyBranchUnionAuditController {
String content = "亲爱的%s老师:欢迎您加入杭州医学院大家庭!您的工会关系在%s,愿您在这个充满活力的集体中,感受“家”的温馨,汇聚“爱”的力量。学校工会将始终陪伴在您身边,竭诚为您服务,期待与您携手同行,共同书写属于“杭医人”的精彩!"
.formatted(user.getUsername(), union.getUnionname());
System.out.println(content);
msgApi.sendMsg(List.of("DingTalk"), record.getLoginname(), 1, "", content, "", "");
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(record.getLoginname()), 1, "", content, "", "","人员管理");
} else {
// 杭州医学院,分工会拒绝接收,还要去创建校工会的审核任务
MemberApplyToDoHandler.REFUSE_UNION_TASK.exec(record, NutMap.NEW().addv("nodeName", "分工会拒绝接收"));
@@ -193,7 +193,7 @@ public class MemberApplyBranchUnionAuditController {
.formatted(union.getUnionname(), record.getUsername(), audit.getAuditOpinion());
String linkUrl = Globals.AppDomain + "/platform/member/apply/schoolUnion/audit/h5";
System.out.println(content);
msgApi.sendMsg(List.of("DingTalk"), record.getLoginname(), 2, "入会结果通知", content, "", linkUrl);
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(record.getLoginname()), 2, "入会结果通知", content, "", linkUrl,"人员管理");
}
return null;
}
@@ -144,7 +144,7 @@ public class MemberApplyController {
String content = "%s老师正申请加入工会,请您点击此条消息或登录“智慧工会”进行审核".formatted(record.getUsername());
String linkUrl = Globals.AppDomain + "/platform/member/apply/schoolUnion/audit/h5";
msgApi.sendMsg(List.of("DingTalk"), schoolLoginNameListStr, 2, "入会审核通知", content, "", linkUrl);
msgApi.sendMsgInsertLog(List.of("DingTalk"), schoolLoginNameList, 2, "入会审核通知", content, "", linkUrl,"人员管理");
return null;
}
@@ -156,7 +156,7 @@ public class MemberApplySchoolUnionAuditController {
String content = "%s,%s老师的入会申请已经通过校工会审核,现将工会关系转入您处,请点击此消息或登录“智慧工会”办理新会员接收手续。"
.formatted(unionname, record.getUsername());
System.out.println(content);
msgApi.sendMsg(List.of("DingTalk"), map.getString("loginname"), 2, "会员入会审核通知", content, "", linkUrl);
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(map.getString("loginname")), 2, "会员入会审核通知", content, "", linkUrl,"人员管理");
}
}
} else {
@@ -164,7 +164,7 @@ public class MemberApplySchoolUnionAuditController {
// 短信通知
String content = "尊敬的%s老师,您的入会申请未被通过,若有疑问请联系校工会,联系电话87692636".formatted(record.getUsername());
System.out.println(content);
msgApi.sendMsg(List.of("DingTalk"), record.getLoginname(), 1, "会员入会结果通知", content, "", "");
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(record.getLoginname()), 1, "会员入会结果通知", content, "", "","人员管理");
}
return null;
}
@@ -286,7 +286,7 @@ public class SourceChangeManageServiceImpl extends BaseServiceImpl<SourceChangeM
String context = "尊敬的%s老师,欢迎您加入杭州医学院大家庭,请您点击此条信息或登录“智慧工会”平台申请成为杭医工会会员!"
.formatted(middleTable.getUsername());
String link = Globals.AppDomain + "/platform/member/apply/submit/h5";
msgApi.sendMsg(List.of("DingTalk"), middleTable.getLoginname(), 2, "入会邀请", context, "", link);
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(middleTable.getLoginname()), 2, "入会邀请", context, "", link,"人员管理");
}
@@ -334,7 +334,7 @@ public class SourceChangeManageServiceImpl extends BaseServiceImpl<SourceChangeM
.formatted(beforeUnionMap.getString("unionname"), middleTable.getUsername(), afterUnionMap.getString("unionname"));
System.out.println(beforeContent);
msgApi.sendMsg(List.of("DingTalk"), map.getString("loginname"), 1, "工会关系转出通知", beforeContent, "", "");
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(map.getString("loginname")), 1, "工会关系转出通知", beforeContent, "", "","人员管理");
}
}
@@ -361,13 +361,13 @@ public class SourceChangeManageServiceImpl extends BaseServiceImpl<SourceChangeM
.formatted(afterUnionMap.getString("unionname"), middleTable.getUsername(), beforeUnionMap.getString("unionname"));
System.out.println(afterContent);
msgApi.sendMsg(List.of("DingTalk"), map.getString("loginname"), 1, "工会关系转入通知", afterContent, "", "");
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(map.getString("loginname")), 1, "工会关系转入通知", afterContent, "", "","人员管理");
}
}
// 发送消息给个人告知异动到了新工会
String content = "亲爱的%s老师,欢迎您加入%s".formatted(middleTable.getUsername(), afterUnionMap.getString("unionname"));
msgApi.sendMsg(List.of("DingTalk"), middleTable.getLoginname(), 1, "", content, "", "");
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(middleTable.getLoginname()), 1, "", content, "", "","人员管理");
}
@@ -753,7 +753,7 @@ public class WelfareListController {
@Param("content") String content, @Param("loginname") String loginname) {
if (StrUtil.isNotBlank(loginname)) {
String link = Globals.AppDomain + "/mobile/welfare/list/receive?projectId=%s".formatted(projectId);
msgApi.sendMsg(List.of("DingTalk"), loginname, 2, title, content, "", link);
msgApi.sendMsgInsertLog(List.of("DingTalk"), List.of(loginname), 2, title, content, "", link,"职工福利");
}
return null;
}
@@ -320,17 +320,19 @@ public class WelfareProjectMangeController {
""");
sql.setParam("projectId", projectId);
List<NutMap> userList = listService.listMap(sql);
Set<String> loginNameList = userList.stream().map(user -> user.getString("loginname")).collect(Collectors.toSet());
String loginNames = String.join(",", loginNameList);
List<String> loginNameList = userList.stream()
.map(user -> user.getString("loginname"))
.distinct()
.toList();
// WelfareProject project = dao.fetch(WelfareProject.class, projectId);
String link = Globals.AppDomain + "/mobile/welfare/list/receive?projectId=%s".formatted(projectId);
// String imageUrl = Globals.AppDomain + "/file_server/fileStreamPreview?id=" + project.getCover();
// msgApi.sendMsg(List.of("DingTalk"), loginNames, 2, "职工福利", sendMsgValue, "", link);
if ("51e3449b5f234dd58757de63142d6b48".equals(projectId)) {
msgApi.sendMsg(List.of("DingTalk"), loginNames, 2, "答题纪念品!点开有惊喜!", sendMsgValue, "", link);
msgApi.sendMsgInsertLog(List.of("DingTalk"), loginNameList, 2, "答题纪念品!点开有惊喜!", sendMsgValue, "", link,"职工福利");
} else {
msgApi.sendMsg(List.of("DingTalk"), loginNames, 2, title, sendMsgValue, "", link);
msgApi.sendMsgInsertLog(List.of("DingTalk"), loginNameList, 2, title, sendMsgValue, "", link,"职工福利");
}
return null;
}
@@ -245,7 +245,7 @@ public class WelfareEvaluateActivityController {
System.out.println(loginNames);
String link = Globals.AppDomain + "/platform/h5/welfare/evaluate?id=%s&type=welfare".formatted(activityId);
msgApi.sendMsg(List.of("DingTalk"), loginNames, 2, title, sendMsgValue, "", link);
msgApi.sendMsgInsertLog(List.of("DingTalk"), loginNames, 2, title, sendMsgValue, "", link,"职工福利");
return Result.success();
} catch (Exception e) {
e.printStackTrace();
@@ -1,12 +1,12 @@
<template>
<div>
<el-form ref="addform" :model="formData" label-width="120px">
<el-form ref="addform" :model="formData" :rules="formRules" label-width="120px">
<el-row gutter="20">
<el-col :span="24">
<el-form-item label="活动名称" prop="title">
<el-form-item label="标题" prop="title">
<el-input v-model="formData.title" :disabled="title_disabled" clearable
placeholder="请输入活动名称"></el-input>
placeholder="请输入标题"></el-input>
</el-form-item>
</el-col>
</el-row>
@@ -256,13 +256,18 @@
<el-form-item label="发送方式" prop="sendTypes">
<el-checkbox-group v-model="formData.sendTypes" size="medium">
<el-checkbox :disabled="hold" border label="DingTalk">钉钉</el-checkbox>
<!-- <el-checkbox :disabled="hold" border label="1">PC门户</el-checkbox>-->
<!-- <el-checkbox :disabled="hold" border label="2">移动校园</el-checkbox>-->
<!-- <el-checkbox :disabled="hold" border label="4">短信</el-checkbox>-->
<!-- <el-checkbox :disabled="hold" border label="5">微信企业号</el-checkbox>-->
</el-checkbox-group>
</el-form-item>
<el-form-item label="发送链接" prop="link">
<el-input
:disabled="hold"
v-model="formData.link"
placeholder="请输入发送链接">
</el-input>
<div style="color:#ee0a24;">不填写发送链接发送文本消息填写发送链接发送卡片消息</div>
</el-form-item>
<el-form-item label="发送内容" prop="content">
<el-input
:disabled="hold"
@@ -271,7 +276,6 @@
placeholder="请输入内容"
type="textarea">
</el-input>
</el-form-item>
</el-form>
@@ -406,6 +410,12 @@ module.exports = {
fileList: [],
},
importLoading: false,
formRules: {
title: [{required: true, message: '请输入发送标题', trigger: ['blur', 'change']}],
sendTypes: [{required: true, message: '请输入发送方式', trigger: ['blur', 'change']}],
content: [{required: true, message: '请输入发送内容', trigger: ['blur', 'change']}],
}
}
},
watch: {
@@ -1,12 +1,15 @@
/**
*Desc:
*Create by: jug
*Create time:2023/5/8/14:52
*/
<template>
<div>
<el-tabs class="customer-tab" type="card" @tab-click="jump" v-model="tabName">
<el-tab-pane v-for="(tab, index) in tabs" :name="tab.refName" :key="index" :label="tab.name"
v-if="tab.visible"
></el-tab-pane>
<el-tab-pane v-for="(tab, index) in tabs" :name="tab.refName" :key="index" :label="tab.name"></el-tab-pane>
</el-tabs>
<div class="scroll-content" @scroll="onScroll" :style="{height:h+'px'}">
<div v-for="(item,index) in tabs" :key="item.refName" class="scroll-item" v-if="item.visible">
<div v-for="(item,index) in tabs" :key="item.refName" class="scroll-item">
<div class="line-name">
<h5>{{ item.name }}</h5>
</div>
@@ -14,10 +17,6 @@
<slot :name="item.refName"></slot>
</div>
</div>
<div class="scroll-item">
<slot name="audit"></slot>
</div>
</div>
</div>
@@ -38,92 +37,100 @@ module.exports = {
},
data() {
return {
tabName: null
tabName: null,
tabIndex: 0,
}
},
methods: {
jump(tab, event) {
let target = document.querySelector('.scroll-content')
let scrollItems = document.querySelectorAll('.scroll-item')
//
if (target.scrollHeight <= target.scrollTop + target.clientHeight) {
this.tabIndex = tab.index.toString()
}
let totalY = scrollItems[tab.index].offsetTop - scrollItems[0].offsetTop // offsetParent(body)()
let distance = document.querySelector('.scroll-content').scrollTop //
// let distance = document.body.scrollTop || document.documentElement.scrollTop || window.pageYOffset // ()
// , 使setTimeout5010ms
//
let step = totalY / 50
if (totalY > distance) {
smoothDown(document.querySelector('.scroll-content'))
} else {
let newTotal = distance - totalY
step = newTotal / 50
smoothUp(document.querySelector('.scroll-content'))
}
const scrollItems = this.$el.querySelectorAll('.scroll-item')
const targetItem = scrollItems[tab.index]
// element
function smoothDown(element) {
if (distance < totalY) {
distance += step
element.scrollTop = distance
setTimeout(smoothDown.bind(this, element), 10)
} else {
element.scrollTop = totalY
}
}
// element
function smoothUp(element) {
if (distance > totalY) {
distance -= step
element.scrollTop = distance
setTimeout(smoothUp.bind(this, element), 10)
} else {
element.scrollTop = totalY
}
}
console.log(this.tabName)
// scrollIntoView
targetItem?.scrollIntoView({behavior: 'auto', block: 'start'})
this.tabName = tab.name
},
onScroll(e) {
if (e.target.scrollTop === 0) {
this.tabName = this.tabs[0].refName
const container = e.target
const {scrollTop, scrollHeight, clientHeight} = container
// 1. 1px
if (scrollTop + clientHeight >= scrollHeight - 1) {
const lastIndex = this.tabs.length - 1
if (this.tabIndex !== lastIndex) {
this.tabIndex = lastIndex
this.tabName = this.tabs[lastIndex]?.refName
}
return //
}
// 2. scrollTop 0
if (scrollTop === 0) {
if (this.tabIndex !== 0) {
this.tabIndex = 0
this.tabName = this.tabs[0]?.refName
}
return
}
$('#app > div.guava-main-content > span > div.el-card').each((idx, item) => {
if ($(item).is(':visible')) {
const scrollItems = $(item).find('.scroll-item')
for (let i = scrollItems.length - 1; i >= 0; i--) {
let judge = e.target.scrollTop >= scrollItems[i].offsetTop - scrollItems[0].offsetTop - 300
if (judge) {
this.tabIndex = i.toString()
this.tabName = this.tabs[this.tabIndex].refName
break
}
// 3. jQuery this.$el
const scrollItems = this.$el.querySelectorAll('.scroll-item')
const threshold = 100 //
for (let i = scrollItems.length - 1; i >= 0; i--) {
const itemTop = scrollItems[i].offsetTop - scrollItems[0].offsetTop
if (scrollTop + threshold >= itemTop) {
if (this.tabIndex !== i) {
this.tabIndex = i
this.tabName = this.tabs[i]?.refName
}
break
}
}
},
scrollToTop(){
const tryScroll = () => {
const scrollEl = this.$el.querySelector('.scroll-content')
if (!scrollEl) return
// scrollHeight > clientHeight
if (scrollEl.scrollHeight <= scrollEl.clientHeight) {
// 100ms
setTimeout(tryScroll, 100)
return
}
//
scrollEl.scrollTo({ top: 0, behavior: 'auto' })
this.tabIndex = 0
this.tabName = this.tabs[0]?.refName
}
this.$nextTick(() => {
setTimeout(tryScroll, 200)
})
},
scrollEnd() {
this.$nextTick(() => {
// slot 200~400ms
setTimeout(() => {
$('#app > div.guava-main-content > span > div.el-card').each((idx, item) => {
if ($(item).is(':visible')) {
const scrollContent = $(item).find('.scroll-content')[0]
scrollContent.scrollTop = scrollContent.scrollHeight
}
})
// document.querySelector('.scroll-content').scrollTop = document.querySelector('.scroll-content').scrollHeight
this.tabName = 'audit'
const scrollEl = this.$el.querySelector('.scroll-content')
if (!scrollEl || !this.tabs?.length) return
// 1.
scrollEl.scrollTop = scrollEl.scrollHeight
// 2. tab
const lastIndex = this.tabs.length - 1
this.tabIndex = lastIndex
this.tabName = this.tabs[lastIndex].refName
// 3. onScroll
this.onScroll?.({ target: scrollEl })
}, 250)
})
}
},
},
created() {
@@ -175,7 +182,7 @@ module.exports = {
.line-name::before {
content: "";
width: 5px;
background: #11879f;
background: rgb(24, 103, 176);
display: inline-block;
position: absolute;
left: 0;
@@ -207,4 +214,4 @@ module.exports = {
color: #fff;
}
</style>
</style>
@@ -1,471 +1,523 @@
<template>
<el-tabs tab-position="top" v-model="activeName" v-loading="loading">
<div>
<el-tab-plus :tabs="tabs" :h="elTabPlusScrollHeight" ref="etp">
<template #basic-info>
<el-descriptions border class="custom-desc">
<el-descriptions-item label="提案名称" span="3">
<div style="font-weight:bold;">
{{ viewData.proposalName }}
</div>
</el-descriptions-item>
<el-descriptions-item label="提案编号">{{ viewData.proposalCode }}</el-descriptions-item>
<el-descriptions-item label="提案人">{{
viewData.createUserName + "-" + viewData.loginname
}}
</el-descriptions-item>
<el-descriptions-item label="提案时间">{{ viewData.createTime }}</el-descriptions-item>
<el-descriptions-item label="教代会届次"> {{ viewData.meetingName || "暂无" }}</el-descriptions-item>
<el-descriptions-item label="提案人工会">{{ viewData.unionName }}</el-descriptions-item>
<el-descriptions-item :label="viewData.mannerCode!=='W03'?'代表团名称':'委员会名称'">
{{ viewData.mannerCode !== "W03" ? viewData.delegationName : viewData.committeeName }}
</el-descriptions-item>
<el-tab-pane label="提案基础信息" name="1">
<el-descriptions border>
<el-descriptions-item label="提案名称" span="3">
<div style="font-weight:bold;">
{{ viewData.proposalName }}
</div>
</el-descriptions-item>
<el-descriptions-item label="提案编号">{{ viewData.proposalCode }}</el-descriptions-item>
<el-descriptions-item label="提案人">{{
viewData.createUserName + "-" + viewData.loginname
}}
</el-descriptions-item>
<el-descriptions-item label="提案时间">{{ viewData.createTime }}</el-descriptions-item>
<el-descriptions-item label="教代会届次"> {{ viewData.meetingName || "暂无" }}</el-descriptions-item>
<el-descriptions-item label="提案人工会">{{ viewData.unionName }}</el-descriptions-item>
<el-descriptions-item :label="viewData.mannerCode!=='W03'?'代表团名称':'委员会名称'">
{{ viewData.mannerCode !== "W03" ? viewData.delegationName : viewData.committeeName }}
</el-descriptions-item>
<el-descriptions-item label="提案类型">{{ viewData.typeName }}</el-descriptions-item>
<el-descriptions-item label="立案结果" span="2"> {{
viewData.resultName ? viewData.resultName : "暂无"
}}
</el-descriptions-item>
<!-- <el-descriptions-item label="提案方式" >{{ viewData.mannerName }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="建议落实部门" >{{ viewData.implementUnitName }}</el-descriptions-item>-->
<el-descriptions-item label="提案类型">{{ viewData.typeName }}</el-descriptions-item>
<el-descriptions-item label="立案结果" span="2"> {{
viewData.resultName ? viewData.resultName : "暂无"
}}
</el-descriptions-item>
<!-- <el-descriptions-item label="提案方式" >{{ viewData.mannerName }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="建议落实部门" >{{ viewData.implementUnitName }}</el-descriptions-item>-->
<el-descriptions-item label="提案内容" span="3">
<div class="text-left" v-html="viewData.brief"></div>
</el-descriptions-item>
<el-descriptions-item label="可行性分析" span="3">
<div class="text-left" v-html="viewData.measures"></div>
</el-descriptions-item>
<el-descriptions-item label="附件" span="3">
<file-upload v-if="viewData.files&&viewData.files.length"
:files="viewData.files" :view="true"></file-upload>
<span v-else></span>
</el-descriptions-item>
<el-descriptions-item label="签&emsp;&emsp;字" span="3">
<el-image v-if="viewData.createUserSign" :src="viewData.createUserSign" class="item-sign"
fit="contain"></el-image>
<span v-else></span>
</el-descriptions-item>
</el-descriptions>
<el-descriptions-item label="提案内容" span="3">
<div class="text-left" v-html="viewData.brief"></div>
</el-descriptions-item>
<el-descriptions-item label="可行性分析" span="3">
<div class="text-left" v-html="viewData.measures"></div>
</el-descriptions-item>
<el-descriptions-item label="附件" span="3">
<file-upload v-if="viewData.files&&viewData.files.length"
:files="viewData.files" :view="true"></file-upload>
<span v-else></span>
</el-descriptions-item>
<el-descriptions-item label="签&emsp;&emsp;字" span="3">
<el-image v-if="viewData.createUserSign" :src="viewData.createUserSign" class="item-sign"
fit="contain"></el-image>
<span v-else></span>
</el-descriptions-item>
</el-descriptions>
<slot name="seconded"></slot>
</template>
<slot name="seconded"></slot>
</el-tab-pane>
<el-tab-pane label="提案并案信息" name="99" v-if="viewData.isConjoin !== 0">
<el-table :data="viewData.conJoinList" border>
<el-table-column prop="proposalCode" label="提案编号"></el-table-column>
<el-table-column prop="username" label="提案人"></el-table-column>
<el-table-column prop="proposalName" label="提案名称">
<template slot-scope="{row}">
<template #conjoin-info>
<el-table :data="viewData.conJoinList" border>
<el-table-column prop="proposalCode" label="提案编号"></el-table-column>
<el-table-column prop="username" label="提案人"></el-table-column>
<el-table-column prop="proposalName" label="提案名称">
<template slot-scope="{row}">
<span @click="openView(row.id)"
style="color: #236eb4; cursor: pointer; text-decoration: underline">{{ row.proposalName }}</span>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="附议信息" name="2" v-if="viewData.seconded">
<el-table :data="viewData.seconded" border>
<el-table-column prop="username" label="姓名"></el-table-column>
<el-table-column prop="unitname" label="单位"></el-table-column>
<el-table-column label="邀请时间" prop="inviteTime">
<template slot-scope="{row}">
{{ row.inviteTime }}
</template>
</el-table-column>
<el-table-column prop="secondedTime" label="附议时间"></el-table-column>
<el-table-column prop="isAgree" label="附议结果">
<template slot-scope="{row}">
<template v-if="row.isAgree==null">
<span class="text-muted">未附议</span>
</template>
<template v-else>
<span v-if="row.isAgree" class="text-success">同意</span>
<span v-else class="text-danger">拒绝</span>
</template>
</template>
</el-table-column>
<el-table-column header-align="center" align="center" prop="signData" label="附议人签字">
<template slot-scope="{row}">
<el-image class="item-sign" :src="row.signData" fit="contain" v-if="row.signData"></el-image>
<span v-else>暂无</span>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="团长审核" name="3" v-if="viewData.delegationAudit&&viewData.delegationAudit.length>0">
<el-descriptions border>
<template v-for="(o,i) in viewData.delegationAudit">
<el-descriptions-item label="审核人">{{ o.username + "-" + o.loginName }}</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ o.auditTime }}</el-descriptions-item>
<el-descriptions-item label="审核结果">
{{ o.flag ? "通过" : "退回" }}
</el-descriptions-item>
<el-descriptions-item span="3" label="审核意见">{{ o.opinion }}</el-descriptions-item>
<el-descriptions-item span="3" label="团长签字">
<template>
<image class="item-sign" :src="o.auditSign " fit="contain" v-if="o.auditSign"></image>
<span v-else>暂无</span>
</template>
</el-descriptions-item>
</template>
</el-descriptions>
</el-tab-pane>
<el-tab-pane label="提案工作组意见" name="4"
v-if="viewData.membersOpinions && viewData.membersOpinions.length>0">
<template v-for="(o,i) in viewData.membersOpinions">
<el-descriptions border :column="3">
<el-descriptions-item label="审核人">
{{ o.username + "-" + o.loginName }}
</el-descriptions-item>
<el-descriptions-item label="立案意见">
{{ o.dictName || "暂无" }}
</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ o.auditTime }}</el-descriptions-item>
<el-descriptions-item label="审核意见" span="3">{{ o.opinion }}</el-descriptions-item>
<el-descriptions-item label="签字" span="3">
<el-image v-if="o.auditSign"
style="width: 300px; height: 100px"
:src="o.auditSign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</el-descriptions>
</template>
</el-tab-pane>
</el-table-column>
</el-table>
</template>
<el-tab-pane v-if="viewData.caseAuditId && viewData.caseAudit" label="提案工作组立案审核" name="5">
<template v-for="(o,i) in viewData.caseAudit">
<el-descriptions border :column="3">
<el-descriptions-item label="审核人">
{{ o.username + "-" + o.loginName }}
</el-descriptions-item>
<!-- <el-descriptions-item label="立案结果">
{{ viewData.resultName ? viewData.resultName : '暂无' }}
&lt;!&ndash; {{ viewData.determineTypeCode ? '(' + viewData.laLevelName + ')' : '' }}&ndash;&gt;
</el-descriptions-item>-->
<el-descriptions-item label="审核结果">
{{ o.flag ? "通过" : "退回修改" }}
</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ o.auditTime }}</el-descriptions-item>
<el-descriptions-item v-if="hostUnit!=''" label="承办单位" span="3">
{{ hostUnit }}主办
<template v-if="helpUnit&&helpUnit.length>0">
{{ helpUnit }}协办
</template>
</el-descriptions-item>
<!-- <el-descriptions-item label="承办单位" span="3" v-if="hostUnit!=''">
{{ hostUnit }}主办
{{ helpUnit }}协办
</el-descriptions-item>
<el-descriptions-item v-if="viewData.resultCode!=='notGive'" label="承办单位" span="3">
<template v-for="(item,index) in viewData.undertake">
{{ item.unitName }}
({{ item.undertakeType === 1 ? '主办' : '协办' }})
{{ index + 1 === viewData.undertake.length ? '' : '、' }}
</template>
</el-descriptions-item>-->
<el-descriptions-item label="审核意见" span="3">{{ o.opinion }}</el-descriptions-item>
<el-descriptions-item label="签字" span="3">
<el-image v-if="o.auditSign"
style="width: 300px; height: 100px"
:src="o.auditSign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</el-descriptions>
<template #seconded-info>
<el-table :data="viewData.seconded" border>
<el-table-column prop="username" label="姓名"></el-table-column>
<el-table-column prop="unitname" label="单位"></el-table-column>
<el-table-column label="邀请时间" prop="inviteTime">
<template slot-scope="{row}">
{{ row.inviteTime }}
</template>
</el-tab-pane>
</el-table-column>
<el-table-column prop="secondedTime" label="附议时间"></el-table-column>
<el-table-column prop="isAgree" label="附议结果">
<template slot-scope="{row}">
<template v-if="row.isAgree==null">
<span class="text-muted">未附议</span>
</template>
<template v-else>
<span v-if="row.isAgree" class="text-success">同意</span>
<span v-else class="text-danger">拒绝</span>
</template>
</template>
</el-table-column>
</el-table>
</template>
<el-tab-pane label="承办单位意见" name="6"
v-if="viewData.underTakeFirstOpinion&&viewData.underTakeFirstOpinion.length>0">
<el-descriptions border>
<template v-for="(o,i) in viewData.underTakeFirstOpinion">
<el-descriptions-item label="承办单位">{{ o.underTakeName }}</el-descriptions-item>
<el-descriptions-item label="时间">{{ o.opionTime }}</el-descriptions-item>
<el-descriptions-item label="审核结果">
<el-tag v-if="o.canTake">同意承办</el-tag>
<el-tag v-else type="danger">无法承办</el-tag>
<template #delegation-audit-info>
<el-descriptions border>
<template v-for="(o,i) in viewData.delegationAudit">
<el-descriptions-item label="审核人">{{ o.username + "-" + o.loginName }}</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ o.auditTime }}</el-descriptions-item>
<el-descriptions-item label="审核结果">
{{ o.flag ? "通过" : "退回" }}
</el-descriptions-item>
<el-descriptions-item span="3" label="审核意见">{{ o.opinion }}</el-descriptions-item>
<el-descriptions-item span="3" label="团长签字">
<template>
<image class="item-sign" :src="o.auditSign " fit="contain" v-if="o.auditSign"></image>
<span v-else>暂无</span>
</template>
</el-descriptions-item>
</template>
</el-descriptions>
</template>
<template #members-opinions-info>
<template v-for="(o,i) in viewData.membersOpinions">
<el-descriptions border :column="3">
<el-descriptions-item label="审核人">
{{ o.username + "-" + o.loginName }}
</el-descriptions-item>
<el-descriptions-item label="立案意见">
{{ o.dictName || "暂无" }}
</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ o.auditTime }}</el-descriptions-item>
<el-descriptions-item label="审核意见" span="3">{{ o.opinion }}</el-descriptions-item>
<el-descriptions-item label="签字" span="3">
<el-image v-if="o.auditSign"
style="width: 300px; height: 100px"
:src="o.auditSign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</el-descriptions>
</template>
</template>
<template #case-audit-info>
<template v-for="(o,i) in viewData.caseAudit">
<el-descriptions border :column="3">
<el-descriptions-item label="审核人">
{{ o.username + "-" + o.loginName }}
</el-descriptions-item>
<!-- <el-descriptions-item label="立案结果">
{{ viewData.resultName ? viewData.resultName : '暂无' }}
&lt;!&ndash; {{ viewData.determineTypeCode ? '(' + viewData.laLevelName + ')' : '' }}&ndash;&gt;
</el-descriptions-item>-->
<el-descriptions-item label="审核结果">
{{ o.flag ? "通过" : "退回修改" }}
</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ o.auditTime }}</el-descriptions-item>
<el-descriptions-item v-if="hostUnit!=''" label="承办单位" span="3">
{{ hostUnit }}主办
<template v-if="helpUnit&&helpUnit.length>0">
{{ helpUnit }}协办
</template>
</el-descriptions-item>
<!-- <el-descriptions-item label="承办单位" span="3" v-if="hostUnit!=''">
{{ hostUnit }}主办
{{ helpUnit }}协办
</el-descriptions-item>
<el-descriptions-item span="3" label="意见">{{ o.opinion }}</el-descriptions-item>
</template>
</el-descriptions>
</el-tab-pane>
<el-tab-pane label="提案工作组确认承办单位" name="10" v-if="viewData.caseUnitAuditId && viewData.caseUnitAudit">
<el-descriptions border>
<el-descriptions-item label="审核人">
{{ viewData.caseUnitAudit.username + "-" + viewData.caseUnitAudit.loginName }}
</el-descriptions-item>
<el-descriptions-item label="审核结果">{{ viewData.resultName }}</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ viewData.caseUnitAudit.auditTime }}</el-descriptions-item>
<el-descriptions-item label="承办单位" span="3" v-if="viewData.resultCode!=='notGive'">
<template v-for="(item,index) in viewData.undertake">
<el-descriptions-item v-if="viewData.resultCode!=='notGive'" label="承办单位" span="3">
<template v-for="(item,index) in viewData.undertake">
{{ item.unitName }}
({{ item.undertakeType === 1 ? "主办" : "协办" }})
{{ index + 1 === viewData.undertake.length ? "" : "、" }}
</template>
</el-descriptions-item>
<el-descriptions-item label="审核意见" span="3">{{ viewData.caseUnitAudit.opinion }}
</el-descriptions-item>
<el-descriptions-item label="签字" span="3">
<el-image v-if="viewData.caseUnitAudit.auditSign"
style="width: 300px; height: 100px"
:src="viewData.caseUnitAudit.auditSign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
({{ item.undertakeType === 1 ? '主办' : '协办' }})
{{ index + 1 === viewData.undertake.length ? '' : '、' }}
</template>
</el-descriptions-item>-->
<el-descriptions-item label="审核意见" span="3">{{ o.opinion }}</el-descriptions-item>
<el-descriptions-item label="签字" span="3">
<el-image v-if="o.auditSign"
style="width: 300px; height: 100px"
:src="o.auditSign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</el-descriptions>
</template>
</template>
<el-tab-pane label="分管校领导批示" name="8" v-if="viewData.branchLeaderOpinion && viewData.branchLeaderOpinion.length>0">
{{ viewData.branchLeaderOpinion }}
<template v-if="viewData.branchLeaderOpinion && viewData.branchLeaderOpinion.length>0">
<el-descriptions :column="2" border class="wrap-table">
<template v-for="(o,i) in viewData.branchLeaderOpinion">
<el-descriptions-item label="审批人">{{ o.username }}</el-descriptions-item>
<el-descriptions-item label="审批时间">{{ o.auditTime }}</el-descriptions-item>
<el-descriptions-item span="4" label="审批意见">{{ o.opinion }}</el-descriptions-item>
<el-descriptions-item span="4" label="分管校领导签字">
<el-image v-if="o.auditSign"
style="width: 300px; height: 100px"
:src="o.auditSign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</template>
</el-descriptions>
<template #undertake-first-audit-info>
<el-descriptions border>
<template v-for="(o,i) in viewData.underTakeFirstOpinion">
<el-descriptions-item label="承办单位">{{ o.underTakeName }}</el-descriptions-item>
<el-descriptions-item label="时间">{{ o.opionTime }}</el-descriptions-item>
<el-descriptions-item label="审核结果">
<el-tag v-if="o.canTake">同意承办</el-tag>
<el-tag v-else type="danger">无法承办</el-tag>
</el-descriptions-item>
<el-descriptions-item span="3" label="意见">{{ o.opinion }}</el-descriptions-item>
</template>
</el-descriptions>
</template>
<template #case-unit-audit-info>
<el-descriptions border>
<el-descriptions-item label="审核人">
{{ viewData.caseUnitAudit.username + "-" + viewData.caseUnitAudit.loginName }}
</el-descriptions-item>
<el-descriptions-item label="审核结果">{{ viewData.resultName }}</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ viewData.caseUnitAudit.auditTime }}</el-descriptions-item>
<el-descriptions-item label="承办单位" span="3" v-if="viewData.resultCode!=='notGive'">
<template v-for="(item,index) in viewData.undertake">
{{ item.unitName }}
({{ item.undertakeType === 1 ? "主办" : "协办" }})
{{ index + 1 === viewData.undertake.length ? "" : "、" }}
</template>
</el-tab-pane>
</el-descriptions-item>
<el-descriptions-item label="审核意见" span="3">{{ viewData.caseUnitAudit.opinion }}
</el-descriptions-item>
<el-descriptions-item label="签字" span="3">
<el-image v-if="viewData.caseUnitAudit.auditSign"
style="width: 300px; height: 100px"
:src="viewData.caseUnitAudit.auditSign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</el-descriptions>
</template>
<el-tab-pane label="承办单位办理" name="11"
v-if="viewData.replyInfo && viewData.replyInfo.length>0 && viewData.replyInfo.some(v=>v.isReply || v.leaderCheckResult!=null)">
<template v-for="(o,i) in viewData.replyInfo.filter(v=>v.isReply || v.leaderCheckResult!=null)">
<el-descriptions border column="3" style="margin-top: 10px">
<el-descriptions-item label="承办单位">{{
o.unitName
}}({{ o.undertakeType === 1 ? "主办" : "协办" }})
</el-descriptions-item>
<el-descriptions-item label="办理">{{ o.dfrUserName }}</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ o.replyTime }}</el-descriptions-item>
<el-descriptions-item label="办理次数">{{ o.replyNumber }}</el-descriptions-item>
<el-descriptions-item label="落实情况" span="2">{{ o.implementState }}</el-descriptions-item>
<el-descriptions-item label="办理内容" span="3">
<div class="text-left" v-html="o.replyContent"></div>
</el-descriptions-item>
<el-descriptions-item label="承办单位签字" span="3">
<el-image v-if="o.replySignData"
style="width: 300px; height: 100px"
:src="o.replySignData"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
<el-descriptions-item label="附件" span="3">
<file-upload v-if="JSON.parse(o.replyFiles)&&JSON.parse(o.replyFiles).length"
:files="JSON.parse(o.replyFiles)" :view="true"></file-upload>
<span v-else></span>
</el-descriptions-item>
</el-descriptions>
<template #undertake-reply-audit-info>
<template v-for="(o,i) in viewData.replyInfo.filter(v=>v.isReply || v.leaderCheckResult!=null)">
<el-descriptions border column="3" style="margin-top: 10px">
<el-descriptions-item label="承办单位">{{
o.unitName
}}({{ o.undertakeType === 1 ? "主办" : "协办" }})
</el-descriptions-item>
<el-descriptions-item label="办理人">{{ o.dfrUserName }}</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ o.replyTime }}</el-descriptions-item>
<el-descriptions-item label="办理次数">{{ o.replyNumber }}</el-descriptions-item>
<el-descriptions-item label="落实情况" span="2">{{ o.implementState }}</el-descriptions-item>
<el-descriptions-item label="办理内容" span="3">
<div class="text-left" v-html="o.replyContent"></div>
</el-descriptions-item>
<el-descriptions-item label="承办单位签字" span="3">
<el-image v-if="o.replySignData"
style="width: 300px; height: 100px"
:src="o.replySignData"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
<el-descriptions-item label="附件" span="3">
<file-upload v-if="JSON.parse(o.replyFiles)&&JSON.parse(o.replyFiles).length"
:files="JSON.parse(o.replyFiles)" :view="true"></file-upload>
<span v-else></span>
</el-descriptions-item>
</el-descriptions>
</template>
</template>
<template #branch-leader-reply-opinion-audit-info>
<template
v-if="viewData.branchLeaderSuffixAuditOpinion && viewData.branchLeaderSuffixAuditOpinion.length>0">
<el-descriptions :column="2" border class="wrap-table">
<template v-for="(o,i) in viewData.branchLeaderSuffixAuditOpinion">
<el-descriptions-item label="审批人">{{ o.username }}</el-descriptions-item>
<el-descriptions-item label="审批时间">{{ o.auditTime }}</el-descriptions-item>
<el-descriptions-item span="4" label="审批意见">{{ o.opinion }}</el-descriptions-item>
<el-descriptions-item span="4" label="分管校领导签字">
<el-image v-if="o.auditSign"
style="width: 300px; height: 100px"
:src="o.auditSign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</template>
</el-tab-pane>
</el-descriptions>
</template>
</template>
<el-tab-pane label="分管校领导审批" name="14"
v-if="viewData.branchLeaderSuffixAuditOpinion && viewData.branchLeaderSuffixAuditOpinion.length>0">
<template
v-if="viewData.branchLeaderSuffixAuditOpinion && viewData.branchLeaderSuffixAuditOpinion.length>0">
<el-descriptions :column="2" border class="wrap-table">
<template v-for="(o,i) in viewData.branchLeaderSuffixAuditOpinion">
<el-descriptions-item label="审批人">{{ o.username }}</el-descriptions-item>
<el-descriptions-item label="审批时间">{{ o.auditTime }}</el-descriptions-item>
<el-descriptions-item span="4" label="审批意见">{{ o.opinion }}</el-descriptions-item>
<el-descriptions-item span="4" label="分管校领导签字">
<el-image v-if="o.auditSign"
style="width: 300px; height: 100px"
:src="o.auditSign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</template>
</el-descriptions>
<template #feedback-audit-info>
<el-descriptions border column="4">
<template v-for="(o,i) in viewData.feedback">
<el-descriptions-item label="反馈人">{{ o.username }}({{ o.loginname }})</el-descriptions-item>
<el-descriptions-item label="反馈时间">{{ o.feedbackTime }}</el-descriptions-item>
<el-descriptions-item label="反馈次数">{{ o.feedbackNumber }}</el-descriptions-item>
<el-descriptions-item label="对办理结果评价">{{ o.feedbackResult }}</el-descriptions-item>
<!-- <el-descriptions-item label="对承办单位办理态度评价">{{ o.feedbackCodeByUnderTake }}</el-descriptions-item>-->
<el-descriptions-item label="反馈意见" span="5">
<div class="text-left" v-html="o.feedbackOpinion"></div>
</el-descriptions-item>
<el-descriptions-item label="反馈人签字" span="4">
<el-image v-if="o.scoreSign"
style="width: 300px; height: 100px"
:src="o.scoreSign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</template>
</el-descriptions>
</template>
<template #audit>
<slot name="handle"></slot>
</template>
</el-tab-plus>
</div>
<!-- <el-tabs tab-position="top" v-model="activeName" v-loading="loading">
<el-tab-pane label="分管校领导批示" name="8"
v-if="viewData.branchLeaderOpinion && viewData.branchLeaderOpinion.length>0">
{{ viewData.branchLeaderOpinion }}
<template v-if="viewData.branchLeaderOpinion && viewData.branchLeaderOpinion.length>0">
<el-descriptions :column="2" border class="wrap-table">
<template v-for="(o,i) in viewData.branchLeaderOpinion">
<el-descriptions-item label="审批人">{{ o.username }}</el-descriptions-item>
<el-descriptions-item label="审批时间">{{ o.auditTime }}</el-descriptions-item>
<el-descriptions-item span="4" label="审批意见">{{ o.opinion }}</el-descriptions-item>
<el-descriptions-item span="4" label="分管校领导签字">
<el-image v-if="o.auditSign"
style="width: 300px; height: 100px"
:src="o.auditSign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</template>
</el-tab-pane>
</el-descriptions>
</template>
</el-tab-pane>
<el-tab-pane label="反馈评分信息" name="13" v-if="viewData.feedback&&viewData.feedback.length>0">
<el-descriptions border column="4">
<template v-for="(o,i) in viewData.feedback">
<el-descriptions-item label="反馈人">{{ o.username }}({{ o.loginname }})</el-descriptions-item>
<el-descriptions-item label="反馈时间">{{ o.feedbackTime }}</el-descriptions-item>
<el-descriptions-item label="反馈次数">{{ o.feedbackNumber }}</el-descriptions-item>
<el-descriptions-item label="对办理结果评价">{{ o.feedbackResult }}</el-descriptions-item>
<!-- <el-descriptions-item label="对承办单位办理态度评价">{{ o.feedbackCodeByUnderTake }}</el-descriptions-item>-->
<el-descriptions-item label="反馈意见" span="5">
<div class="text-left" v-html="o.feedbackOpinion"></div>
</el-descriptions-item>
<el-descriptions-item label="反馈人签字" span="4">
<el-image v-if="o.scoreSign"
style="width: 300px; height: 100px"
:src="o.scoreSign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</template>
</el-descriptions>
</el-tab-pane>
<el-tab-pane v-if="handle" :label="label" name="999">
<slot name="handle"></slot>
</el-tab-pane>
</el-tabs>-->
<el-tab-pane v-if="handle" :label="label" name="999">
<slot name="handle"></slot>
</el-tab-pane>
</el-tabs>
</template>
<script>
module.exports = {
props: {
id: String,
handle: {
type: Boolean,
default: false
},
label: {
type: String,
default: "审核"
},
panes: {
type: Array,
default: []
}
props: {
id: String,
handle: {
type: Boolean,
default: false
},
data() {
return {
hostUnit: "",
helpUnit: [],
unitOptions: [],
activeName: "1",
activeCollapse: "",
loading: true,
viewData: {},
config: {}
}
label: {
type: String,
default: "审核"
},
components: {
"file-upload": httpVueLoader("/components/plugins/FileUpload.vue")
},
methods: {
hasPane(name) {
if (!this.handle) {
return true
}
return this.panes.includes(name)
},
async openView(id, activeName = "1") {
this.loading = true
this.viewData = await proposal.getProposalInfo(id)
console.log(this.viewData)
if (!this.viewData) {
this.$notify.error({ title: "失败", message: "获取提案信息失败" })
this.loading = false
return
}
const { files, feedback, caseAudit, resultCode } = this.viewData
this.helpUnit = []
if (caseAudit != null && resultCode !== "notGive" && caseAudit.other != null) {
const other = JSON.parse(caseAudit.other)
if (this.unitOptions.find(v => v.id === other.hostUnit)) {
this.hostUnit = this.unitOptions.find(v => v.id === other.hostUnit).unitName
if (other.helpUnit && other.helpUnit.length > 0) {
other.helpUnit.forEach((v, i) => {
const helpUnit = this.unitOptions.find(z => other.helpUnit[i] === z.id).unitName
this.helpUnit.push(helpUnit)
})
this.helpUnit = this.helpUnit.join("、")
}
}
}
if (files) {
this.viewData.files = JSON.parse(files)
}
if (feedback) {
this.viewData.feedback.map(v => {
v.files = JSON.parse(v.files)
})
}
this.activeName = this.handle ? "999" : activeName
this.loading = false
}
},
async created() {
this.config = await proposal.getProposalConfig()
this.unitOptions = await proposal.getProposalUndertake()
panes: {
type: Array,
default: []
}
},
data() {
return {
hostUnit: "",
helpUnit: [],
unitOptions: [],
activeName: "1",
activeCollapse: "",
loading: true,
viewData: {},
config: {},
tabs: [],
elTabPlusScrollHeight: 700,
}
},
components: {
'el-tab-plus': httpVueLoader('/components/plugins/ELTabPlus.vue'),
"file-upload": httpVueLoader("/components/plugins/FileUpload.vue")
},
methods: {
hasPane(name) {
if (!this.handle) {
return true
}
return this.panes.includes(name)
},
async openView(id) {
this.loading = true
this.viewData = await proposal.getProposalInfo(id)
if (!this.viewData) {
this.$notify.error({title: "失败", message: "获取提案信息失败"})
this.loading = false
return
}
const {files, feedback, caseAudit, resultCode} = this.viewData
this.helpUnit = []
if (caseAudit != null && resultCode !== "notGive" && caseAudit.other != null) {
const other = JSON.parse(caseAudit.other)
if (this.unitOptions.find(v => v.id === other.hostUnit)) {
this.hostUnit = this.unitOptions.find(v => v.id === other.hostUnit).unitName
if (other.helpUnit && other.helpUnit.length > 0) {
other.helpUnit.forEach((v, i) => {
const helpUnit = this.unitOptions.find(z => other.helpUnit[i] === z.id).unitName
this.helpUnit.push(helpUnit)
})
this.helpUnit = this.helpUnit.join("、")
}
}
}
if (files) {
this.viewData.files = JSON.parse(files)
}
if (feedback) {
this.viewData.feedback.map(v => {
v.files = JSON.parse(v.files)
})
}
this.formatTab()
this.loading = false
},
formatTab() {
let tabs = []
tabs.push({name: '提案基础信息', refName: 'basic-info'})
if (this.viewData.isConjoin !== 0) {
tabs.push({name: '提案并案信息', refName: 'conjoin-info'})
}
if (this.viewData.seconded) {
tabs.push({name: '附议信息', refName: 'seconded-info'})
}
if (this.viewData.delegationAudit && this.viewData.delegationAudit.length > 0) {
tabs.push({name: '团长审核', refName: 'delegation-audit-info'})
}
if (this.viewData.membersOpinions && this.viewData.membersOpinions.length > 0) {
tabs.push({name: '提案工作组意见', refName: 'members-opinions-info'})
}
if (this.viewData.caseAuditId && this.viewData.caseAudit) {
tabs.push({name: '提案工作组立案审核', refName: 'case-audit-info'})
}
if (this.viewData.underTakeFirstOpinion && this.viewData.underTakeFirstOpinion.length > 0) {
tabs.push({name: '承办单位意见', refName: 'undertake-first-audit-info'})
}
if (this.viewData.caseUnitAuditId && this.viewData.caseUnitAudit) {
tabs.push({name: '提案工作组确认承办单位', refName: 'case-unit-audit-info'})
}
if (this.viewData.replyInfo && this.viewData.replyInfo.length > 0 && this.viewData.replyInfo.some(v => v.isReply || v.leaderCheckResult != null)) {
tabs.push({name: '承办单位办理', refName: 'undertake-reply-audit-info'})
}
if (this.viewData.branchLeaderSuffixAuditOpinion && this.viewData.branchLeaderSuffixAuditOpinion.length > 0) {
tabs.push({name: '分管校领导审批', refName: 'branch-leader-reply-opinion-audit-info'})
}
if (this.viewData.feedback && this.viewData.feedback.length > 0) {
tabs.push({name: '反馈评分信息', refName: 'feedback-audit-info'})
}
//
if (this.handle) {
tabs.push({name: this.label, refName: 'audit'})
if (this.$refs.etp) {
this.$refs.etp.scrollEnd()
}
} else {
//
if (this.$refs.etp) {
this.$refs.etp.scrollToTop()
}
}
this.tabs = tabs
},
getHeight() {
const h = window.innerHeight - 50 - 20 - 54 - 40 - 50 - 10 - 10
this.elTabPlusScrollHeight = h
}
},
async created() {
this.config = await proposal.getProposalConfig()
this.unitOptions = await proposal.getProposalUndertake()
this.getHeight()
window.addEventListener('resize', this.getHeight)
}
}
</script>
<style>
.el-descriptions__table {
table-layout: fixed !important;
table-layout: fixed !important;
}
.el-descriptions-item__cell {
text-align: center !important;
text-align: center !important;
}
.item-center {
text-align: center;
font-weight: 700;
margin-right: 110px;
text-align: center;
font-weight: 700;
margin-right: 110px;
}
.item-center .el-form-item__content {
font-size: 18px;
font-size: 18px;
}
.item-sign {
width: 200px;
height: 130px;
width: 200px;
height: 130px;
}
.item-form {
padding: 0 25px;
padding: 0 25px;
}
.el-collapse-item__wrap {
padding: 10px 10px;
padding: 10px 10px;
}
.el-collapse-item__header {
border-bottom: 1px solid #eee;
font-weight: 700;
border-bottom: 1px solid #eee;
font-weight: 700;
}
.item-number {
color: red;
font-weight: 600
color: red;
font-weight: 600
}
.el-collapse-item__header {
position: relative;
position: relative;
}
.el-collapse-item__arrow {
color: #1582dc;
font-weight: 800 !important;
color: #1582dc;
font-weight: 800 !important;
}
/*.el-tabs__header{*/
@@ -504,15 +556,22 @@ module.exports = {
/*}*/
.wrap-table tbody:nth-child(3n+3):after {
content: '';
display: block;
height: 15px;
content: '';
display: block;
height: 15px;
}
.wrap-table-4 tbody:nth-child(4n+4):after {
content: '';
display: block;
height: 15px;
content: '';
display: block;
height: 15px;
}
.custom-desc .el-descriptions-item__label {
width: 15% !important;
}
</style>
@@ -0,0 +1,877 @@
<!--#layout("/mobile/platform.html"){#-->
<style>
:root {
--primary-color: rgb(0, 78, 100);
--primary-light: rgba(0, 78, 100, 0.1);
--secondary-color: #37A6BD;
--text-color: #333333;
--text-secondary: #666666;
--text-light: #999999;
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--border-color: #eeeeee;
--success-color: #07c160;
--warning-color: #ff976a;
}
#app {
min-height: 100vh;
background-color: var(--bg-color);
display: flex;
flex-direction: column;
}
/* 筛选区域样式 */
.filter-section {
background-color: var(--card-bg);
padding: 12px 16px;
margin-bottom: 10px;
overflow: hidden;
transition: max-height 0.3s ease;
}
.filter-section.collapsed {
max-height: 84px;
}
.filter-section.expanded {
max-height: 500px;
}
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.filter-title {
font-size: 15px;
font-weight: 500;
color: var(--text-color);
}
.filter-toggle {
color: var(--text-secondary);
display: flex;
align-items: center;
}
.filter-toggle .van-icon {
transition: transform 0.3s;
margin-left: 4px;
}
.filter-toggle .van-icon.rotate {
transform: rotate(180deg);
}
.filter-content {
transition: opacity 0.3s;
}
.filter-content.hidden {
opacity: 0;
height: 0;
overflow: hidden;
}
.filter-row {
display: flex;
align-items: flex-start;
margin-bottom: 10px;
}
.filter-row:last-child {
margin-bottom: 0;
}
.filter-label {
font-size: 13px;
color: var(--text-secondary);
margin-right: 10px;
min-width: 60px;
padding-top: 4px;
}
.filter-options {
display: flex;
flex-wrap: wrap;
flex: 1;
}
.filter-tag {
padding: 4px 10px;
border-radius: 16px;
font-size: 12px;
margin-right: 8px;
margin-bottom: 6px;
background-color: var(--bg-color);
color: var(--text-secondary);
}
.filter-tag.active {
background-color: var(--primary-light);
color: var(--primary-color);
font-weight: 500;
}
.filter-search {
padding: 8px 0;
}
.filter-search .van-search {
padding: 0;
}
.filter-search .van-search__content {
background-color: var(--bg-color);
}
.suggestion-list {
padding: 16px;
background-color: var(--bg-color);
flex: 1;
display: flex;
flex-direction: column;
}
.van-pull-refresh, .van-list {
flex: 1;
display: flex;
flex-direction: column;
}
.suggestion-card {
background-color: var(--card-bg);
border-radius: 12px;
margin-bottom: 16px;
padding: 16px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
border: 1px solid rgba(0, 0, 0, 0.02);
}
.suggestion-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.suggestion-title {
font-size: 16px;
font-weight: 600;
color: var(--text-color);
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.suggestion-status {
font-size: 12px;
padding: 3px 8px;
border-radius: 12px;
margin-left: 10px;
font-weight: 500;
}
.status-pending {
background-color: #e6f7ff;
color: #1890ff;
}
.status-processing {
background-color: #fff7e6;
color: #fa8c16;
}
.status-completed {
background-color: #f6ffed;
color: #52c41a;
}
.suggestion-content {
font-size: 14px;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 16px;
overflow: hidden;
background-color: var(--bg-color);
padding: 10px;
border-radius: 8px;
word-break: break-all;
white-space: pre-line;
max-height: 3.2em;
text-overflow: ellipsis;
display: block;
}
.suggestion-submitter {
display: flex;
align-items: center;
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 12px;
}
.suggestion-submitter .van-icon {
margin-right: 5px;
font-size: 14px;
}
.suggestion-unit {
margin-left: 15px;
}
.suggestion-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 13px;
color: var(--text-light);
border-top: 1px solid #f5f5f5;
padding-top: 12px;
}
.suggestion-time {
display: flex;
align-items: center;
}
.suggestion-time .van-icon {
font-size: 14px;
margin-right: 4px;
}
.suggestion-action {
color: var(--primary-color);
display: flex;
align-items: center;
font-weight: 500;
}
.suggestion-action .van-icon {
font-size: 14px;
margin-left: 2px;
}
.empty-list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 64px 16px;
flex: 1;
}
.empty-icon {
font-size: 64px;
color: #ddd;
margin-bottom: 16px;
text-align: center;
}
.empty-text {
font-size: 15px;
color: var(--text-light);
text-align: center;
margin-bottom: 20px;
}
/* 详情弹窗样式 */
.detail-popup {
padding: 24px;
max-height: 80vh;
overflow-y: auto;
}
.detail-header {
margin-bottom: 20px;
}
.detail-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 10px;
color: var(--text-color);
}
.detail-meta {
display: flex;
justify-content: space-between;
color: var(--text-light);
font-size: 14px;
}
.detail-section {
margin-bottom: 24px;
}
.detail-label {
color: var(--text-secondary);
margin-bottom: 8px;
font-weight: 500;
font-size: 15px;
}
.detail-content {
color: var(--text-color);
line-height: 1.8;
font-size: 15px;
white-space: pre-wrap;
word-break: break-word;
}
.submitter-info {
background-color: var(--bg-color);
border-radius: 8px;
padding: 12px 15px;
}
.info-item {
display: flex;
align-items: center;
margin-bottom: 8px;
line-height: 1.6;
}
.info-item:last-child {
margin-bottom: 0;
}
.info-label {
color: var(--text-secondary);
width: 80px;
font-size: 14px;
}
.info-value {
color: var(--text-color);
flex: 1;
font-size: 14px;
}
.detail-reply {
background-color: #f9f9f9;
padding: 16px;
border-radius: 8px;
border-left: 4px solid var(--primary-color);
}
.detail-attachments {
display: flex;
flex-wrap: wrap;
}
.attachment-item {
width: 90px;
height: 90px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.attachment-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.reply-form {
margin-top: 16px;
}
.reply-textarea {
box-sizing: border-box;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 8px;
width: 100%;
height: 100px;
font-size: 14px;
background-color: var(--card-bg);
margin-bottom: 16px;
}
.reply-attachments {
margin-bottom: 16px;
}
.reply-actions {
display: flex;
justify-content: space-between;
}
.no-reply {
padding: 20px 0;
text-align: center;
background-color: var(--bg-color);
border-radius: 8px;
}
.no-reply-icon {
font-size: 36px;
color: #ccc;
margin-bottom: 8px;
}
.no-reply-text {
font-size: 14px;
color: var(--text-light);
}
.van-button--primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
/* 下拉刷新和上拉加载样式 */
.van-pull-refresh__track {
flex: 1;
}
.van-list {
min-height: 100%;
}
/* 统计面板 */
.stats-panel {
background-color: var(--card-bg);
padding: 16px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
}
.stats-item {
flex: 1;
text-align: center;
}
.stats-value {
font-size: 20px;
font-weight: bold;
color: var(--primary-color);
}
.stats-label {
font-size: 12px;
color: var(--text-secondary);
margin-top: 4px;
}
</style>
<div id="app" v-cloak>
<van-nav-bar title="意见箱管理" left-arrow @click-left="pjaxReplace('/platform/suggestionBox/h5')" fixed placeholder></van-nav-bar>
<!-- 统计面板 -->
<div class="stats-panel">
<div class="stats-item">
<div class="stats-value">{{ stats.total }}</div>
<div class="stats-label">总意见数</div>
</div>
<div class="stats-item">
<div class="stats-value">{{ stats.pending }}</div>
<div class="stats-label">待回复</div>
</div>
<div class="stats-item">
<div class="stats-value">{{ stats.replied }}</div>
<div class="stats-label">已回复</div>
</div>
<div class="stats-item">
<div class="stats-value">{{ stats.today }}</div>
<div class="stats-label">今日新增</div>
</div>
</div>
<!-- 筛选区域 -->
<div class="filter-section" :class="filters.isCollapsed ? 'collapsed' : 'expanded'">
<div class="filter-header">
<div class="filter-title">筛选条件</div>
<div class="filter-toggle" @click="toggleFilterCollapse">
<span>{{ filters.isCollapsed ? '展开' : '收起' }}</span>
<van-icon :name="filters.isCollapsed ? 'arrow-down' : 'arrow-up'"
:class="{ rotate: !filters.isCollapsed }"></van-icon>
</div>
</div>
<div class="filter-search">
<van-search v-model="filters.keyword" placeholder="搜索意见标题、内容或提交人"
@search="onSearch"></van-search>
</div>
<div class="filter-content" :class="{ hidden: filters.isCollapsed }">
<div class="filter-row">
<div class="filter-label">状态</div>
<div class="filter-options">
<div class="filter-tag" :class="{ active: filters.status === '' }" @click="setFilter('status', '')">
全部
</div>
<div class="filter-tag" :class="{ active: filters.status === '0' }"
@click="setFilter('status', '0')">
待回复
</div>
<div class="filter-tag" :class="{ active: filters.status === '1' }"
@click="setFilter('status', '1')">
已回复
</div>
</div>
</div>
<div class="filter-row">
<div class="filter-label">时间</div>
<div class="filter-options">
<div class="filter-tag" :class="{ active: filters.time === '' }" @click="setFilter('time', '')">全部
</div>
<div class="filter-tag" :class="{ active: filters.time === 'today' }"
@click="setFilter('time', 'today')">今日
</div>
<div class="filter-tag" :class="{ active: filters.time === 'week' }"
@click="setFilter('time', 'week')">
本周
</div>
<div class="filter-tag" :class="{ active: filters.time === 'month' }"
@click="setFilter('time', 'month')">本月
</div>
</div>
</div>
</div>
</div>
<!-- 内容区域 -->
<div class="suggestion-list">
<!-- 下拉刷新和上拉加载更多 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="loadMore"
>
<!-- 空状态 -->
<div class="empty-list" v-if="suggestions.length === 0 && !loading">
<van-icon name="comment-circle-o" class="empty-icon"></van-icon>
<div class="empty-text">暂无符合条件的意见</div>
</div>
<!-- 意见列表 -->
<div class="suggestion-card" v-for="(item, index) in suggestions" :key="item.id"
@click="showDetail(item)">
<div class="suggestion-header">
<div class="suggestion-title">{{ item.title || '意见反馈' }}</div>
<div class="suggestion-status" :class="getStatusClass(item.isReply)">
{{ getStatusText(item.isReply) }}
</div>
</div>
<div class="suggestion-submitter">
<van-icon name="contact"/>
<span>{{ item.submitterName }}</span>
<span class="suggestion-unit">{{ item.submitterUnitName }}</span>
</div>
<div class="suggestion-content">{{ item.content }}</div>
<div class="suggestion-footer">
<div class="suggestion-time">
<van-icon name="clock-o"/>
<span>{{ formatDate(item.submitTime) }}</span>
</div>
<div class="suggestion-action">
{{ item.reply ? '查看详情' : '去回复' }}
<van-icon name="arrow"/>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
<!-- 详情弹出层 -->
<van-popup v-model="showDetailPopup" round closeable position="bottom">
<div class="detail-popup" v-if="currentSuggestion">
<div class="detail-header">
<div class="detail-title">{{ currentSuggestion.title || '意见反馈' }}</div>
<div class="detail-meta">
<span>{{ formatDate(currentSuggestion.submitTime) }}</span>
<span :class="getStatusClass(currentSuggestion.isReply)">{{ getStatusText(currentSuggestion.isReply) }}</span>
</div>
</div>
<div class="detail-section">
<div class="detail-label">提交人信息</div>
<div class="submitter-info">
<div class="info-item">
<span class="info-label">姓名:</span>
<span class="info-value">{{ currentSuggestion.submitterName || '未填写' }}</span>
</div>
<div class="info-item">
<span class="info-label">工号:</span>
<span class="info-value">{{ currentSuggestion.submitterLoginName || '未填写' }}</span>
</div>
<div class="info-item">
<span class="info-label">单位:</span>
<span class="info-value">{{ currentSuggestion.submitterUnitName || '未填写' }}</span>
</div>
<div class="info-item">
<span class="info-label">联系方式:</span>
<span class="info-value">{{ currentSuggestion.concat || '未填写' }}</span>
</div>
</div>
</div>
<div class="detail-section">
<div class="detail-label">意见内容</div>
<div class="detail-content">{{ currentSuggestion.content }}</div>
</div>
<div class="detail-section"
v-if="currentSuggestion.attachments && currentSuggestion.attachments.length > 0">
<div class="detail-label">附件</div>
<div class="detail-attachments">
<vant-file-upload :del="false" :files.sync="currentSuggestion.attachments"
view></vant-file-upload>
<!-- <div class="attachment-item" v-for="(file, idx) in currentSuggestion.attachments" :key="idx"
@click.stop="previewImage(file.url, idx)">
<img :src="file.url" class="attachment-image">
</div>-->
</div>
</div>
<div class="detail-section" v-if="currentSuggestion.reply">
<div class="detail-label">已回复内容</div>
<div class="detail-reply detail-content">{{ currentSuggestion.reply }}</div>
<div class="detail-meta" style="margin-top: 10px;">
回复时间:{{ formatDate(currentSuggestion.replyTime) }}
</div>
<!-- <div v-if="currentSuggestion.replyAttachments && currentSuggestion.replyAttachments.length > 0"
style="margin-top: 12px;">
<div class="detail-label">回复附件</div>
<div class="detail-attachments">
<vant-file-upload :del="false" :files.sync="currentSuggestion.attachments"
view></vant-file-upload>
</div>
</div>-->
</div>
<div class="detail-section">
<div class="detail-label">{{ currentSuggestion.isReply ? '修改回复' : '回复意见' }}</div>
<div class="reply-form">
<textarea class="reply-textarea" v-model="currentSuggestion.replyContent"
placeholder="请输入回复内容..."></textarea>
<!-- <div class="reply-attachments">-->
<!-- <vant-file-upload :files.sync="formData.replyAttachments" :max="15"></vant-file-upload>-->
<!-- </div>-->
<div class="reply-actions">
<van-button style="border-radius: 10px" block type="info" :color="themeColor"
@click="submitReply">提交回复
</van-button>
</div>
</div>
</div>
</div>
</van-popup>
</div>
<script>
new Vue({
el: '#app',
mixins: [mobileMixins],
data() {
return {
refreshing: false,
loading: false,
finished: false,
showDetailPopup: false,
currentSuggestion: null,
pageNumber: 1,
pageSize: 10,
suggestions: [],
replyContent: '',
replyAttachments: [],
filters: {
status: '', // 状态筛选
time: '', // 时间筛选
keyword: '', // 关键词搜索
isCollapsed: true // 筛选区域是否折叠
},
stats: {
total: 0,
pending: 0,
replied: 0,
today: 0
},
formData: {}
}
},
created() {
this.loadStats();
this.loadData();
},
methods: {
// 加载统计数据
loadStats() {
$.post('/platform/suggestionBox/admin/getStats').done((res) => {
if (res.code === 0 && res.data) {
this.stats = {
total: res.data.total || 0,
pending: res.data.pending || 0,
replied: res.data.replied || 0,
today: res.data.today || 0
};
}
}).fail(() => {
this.$toast.fail('统计数据加载失败');
});
},
// 加载意见数据
loadData() {
this.loading = true;
$.post('/platform/suggestionBox/admin/pageData', {
pageNumber: this.pageNumber,
pageSize: this.pageSize,
status: this.filters.status,
timeRange: this.filters.time,
keyword: this.filters.keyword
}).done((res) => {
if (res.code === 0 && res.data) {
if (this.pageNumber === 1) {
this.suggestions = res.data.list || [];
} else {
this.suggestions = this.suggestions.concat(res.data.list || []);
}
this.finished = !res.data.list || res.data.list.length < this.pageSize;
} else {
this.finished = true;
}
this.loading = false;
this.refreshing = false;
}).fail(() => {
this.loading = false;
this.refreshing = false;
this.finished = true;
});
},
// 下拉刷新
onRefresh() {
this.pageNumber = 1;
this.finished = false;
this.loadStats();
this.loadData();
},
// 上拉加载更多
loadMore() {
this.pageNumber++;
this.loadData();
},
// 设置筛选条件
setFilter(type, value) {
this.filters[type] = value;
this.pageNumber = 1;
this.finished = false;
this.loadData();
},
// 搜索
onSearch() {
this.pageNumber = 1;
this.finished = false;
this.loadData();
},
// 查看详情
showDetail(suggestion) {
if (suggestion.attachments && typeof suggestion.attachments === 'string') {
try{
suggestion.attachments = JSON.parse(suggestion.attachments);
}catch (e){
suggestion.attachments = [];
}
}
console.log(suggestion)
this.currentSuggestion = suggestion;
this.showDetailPopup = true;
},
// 提交回复
submitReply() {
if (!this.currentSuggestion.replyContent || !this.currentSuggestion.replyContent.trim()) {
this.$toast('请输入回复内容');
return;
}
this.$dialog.confirm({
title: '确认提交',
message: '确定提交此回复内容吗?'
}).then(() => {
// 提交回复
$.post('/platform/suggestionBox/admin/reply', {
reply: JSON.stringify(this.currentSuggestion)
}).done((res) => {
if (res.code === 0) {
this.$toast.success('回复成功');
this.showDetailPopup = false;
this.loadStats();
this.pageData();
} else {
this.$toast.fail(res.msg || '回复失败');
}
}).fail(() => {
this.$toast.fail('网络错误,请重试');
});
});
},
// 获取状态class
getStatusClass(isReply) {
if (!isReply || isReply === 0) return 'status-pending';
if (isReply === 1) return 'status-processing';
return 'status-completed';
},
// 获取状态文本
getStatusText(isReply) {
if (!isReply || isReply === 0) return '待回复';
if (isReply === 1) return '已回复';
return '已处理';
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0');
},
// 预览图片
previewImage(url, index, type = 'submission') {
if (!url) return;
// 创建图片查看器
const urls = type === 'reply'
? this.currentSuggestion.replyAttachments.map(file => file.url)
: this.currentSuggestion.attachments.map(file => file.url);
this.$imagePreview({
images: urls,
startPosition: index
});
},
// 切换筛选区域折叠状态
toggleFilterCollapse() {
this.filters.isCollapsed = !this.filters.isCollapsed;
}
}
});
</script>
<!--#}#-->
@@ -0,0 +1,531 @@
<!--#
layout("/mobile/platform.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="意见箱" fixed placeholder @click-left="pjaxReplace('/mobile/index')" left-text="返回" left-arrow></van-nav-bar>
<!-- 顶部Banner区域 -->
<div class="banner-section">
<div class="banner-content">
<div class="banner-text">
<h2>我们重视您的意见</h2>
<p>每一条建议都将认真对待</p>
</div>
<div class="banner-image">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNMTI1LjYgMTAyLjdsLTE5LTE2LjhtLTQuNy0yMy43bDExLjUtNi44TTU3LjQgMTZsMTEuNSAxMS43TTMxLjIgNTMuOGwyMy42IDcuOSIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6I2ZmZjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtvcGFjaXR5Oi40Ii8+PHBhdGggZD0iTTY0LjEgMzEuN0w0NyA0OS45Yy0zIDMuMi0zLjEgOC4xLS4xIDExLjJsMjQuNCAyNWMzIDMuMSA3LjkgMy4yIDExIC4xTDk5IDY5YzMtMy4yIDMuMS04LjEuMS0xMS4yTDc0LjcgMzIuOGMtMyAzLjEtNy45IDMuMS0xMC42LTEuMXoiIHN0eWxlPSJmaWxsOiNmZmY7c3Ryb2tlOiNmZmY7c3Ryb2tlLW1pdGVybGltaXQ6MTAiLz48cGF0aCBkPSJNMTA4LjkgOTUuN2wtMTUuOC0xMi0xMi4yIDEzIDE0LjQgMTMuN2MuMyAyLjUgMi4zIDQuNSA0LjggNC41aDEzLjdjMi43IDAgNC45LTIuMiA0LjktNC45VjkzLjVjMC0yLjgtMi4xLTUtNC45LTV2OC40cy4xLTEuMi00LjkgMi44LjEtMyAuMS0zeiIgc3R5bGU9ImZpbGw6I2ZmZjtzdHJva2U6I2ZmZjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMCIvPjwvc3ZnPg=="
alt="Feedback">
</div>
</div>
</div>
<!-- 功能卡片区域 -->
<div class="cards-container">
<!-- 提交意见 -->
<div class="feature-card" @click="goToSubmitPage">
<div class="card-icon submit-icon">
<van-icon name="edit"/>
</div>
<div class="card-info">
<h3>提交意见</h3>
<p>分享您的想法和建议</p>
</div>
<div class="card-arrow">
<van-icon name="arrow"/>
</div>
</div>
<!-- 我的意见 -->
<div class="feature-card" @click="goToMyOpinionsPage">
<div class="card-icon my-icon">
<van-icon name="records"/>
</div>
<div class="card-info">
<h3>我的意见</h3>
<p>查看您提交的所有意见</p>
</div>
<div class="card-arrow">
<van-icon name="arrow"/>
</div>
</div>
<!-- 意见管理(管理员) -->
<div class="feature-card" v-if="isAdmin" @click="goToAllOpinionsPage">
<div class="card-icon admin-icon">
<van-icon name="manager"/>
</div>
<div class="card-info">
<h3>意见管理</h3>
<p>管理所有用户提交的意见</p>
</div>
<div class="card-arrow">
<van-icon name="arrow"/>
</div>
</div>
</div>
<!-- 使用指南 -->
<div class="guide-container" v-if="!isAdmin">
<div class="guide-header">
<h3>使用指南</h3>
</div>
<div class="guide-steps">
<div class="guide-step">
<div class="step-number">1</div>
<div class="step-content">
<h4>提交意见</h4>
<p>点击"提交意见"按钮,填写您的意见和建议</p>
</div>
<div class="step-icon">
<van-icon name="edit"/>
</div>
</div>
<div class="step-divider"></div>
<div class="guide-step">
<div class="step-number">2</div>
<div class="step-content">
<h4>等待处理</h4>
<p>我们将在3个工作日内处理您的意见</p>
</div>
<div class="step-icon">
<van-icon name="underway-o"/>
</div>
</div>
<div class="step-divider"></div>
<div class="guide-step">
<div class="step-number">3</div>
<div class="step-content">
<h4>查看回复</h4>
<p>在"我的意见"中查看官方回复</p>
</div>
<div class="step-icon">
<van-icon name="comment-o"/>
</div>
</div>
</div>
</div>
<!-- 常见问题 -->
<div class="faq-container" v-if="!isAdmin">
<div class="faq-header">
<h3>常见问题</h3>
<span class="faq-subtitle">解答您的疑惑</span>
</div>
<div class="faq-list">
<div class="faq-item" :class="{'faq-active': activeFaq === 1}" @click="toggleFaq(1)">
<div class="faq-question">
<span class="faq-icon"><van-icon name="question-o"/></span>
<span>如何提交带附件的意见?</span>
<span class="faq-arrow"><van-icon name="arrow-down"/></span>
</div>
<div class="faq-answer" v-show="activeFaq === 1">
<p>在提交意见表单中,您可以上传最多3个文件作为附件,支持图片格式。点击附件上传区域,选择您要上传的文件即可。</p>
</div>
</div>
<div class="faq-item" :class="{'faq-active': activeFaq === 2}" @click="toggleFaq(2)">
<div class="faq-question">
<span class="faq-icon"><van-icon name="question-o"/></span>
<span>意见提交后多久能收到回复?</span>
<span class="faq-arrow"><van-icon name="arrow-down"/></span>
</div>
<div class="faq-answer" v-show="activeFaq === 2">
<p>我们会在3个工作日内处理您的意见,紧急问题会优先处理。您可以随时在"我的意见"中查看处理进度。</p>
</div>
</div>
<div class="faq-item" :class="{'faq-active': activeFaq === 3}" @click="toggleFaq(3)">
<div class="faq-question">
<span class="faq-icon"><van-icon name="question-o"/></span>
<span>我可以修改已提交的意见吗?</span>
<span class="faq-arrow"><van-icon name="arrow-down"/></span>
</div>
<div class="faq-answer" v-show="activeFaq === 3">
<p>提交后的意见暂不支持修改,如有补充,请重新提交并说明这是对之前意见的补充。我们会将相关意见关联处理。</p>
</div>
</div>
</div>
</div>
</div>
<style>
:root {
--primary-color: rgb(0, 78, 100);
--primary-light: rgba(0, 78, 100, 0.1);
--secondary-color: #37A6BD;
--text-color: #333333;
--text-secondary: #666666;
--text-light: #999999;
--bg-color: #f6f6f6;
--card-bg: #ffffff;
--border-color: #eeeeee;
--success-color: #07c160;
--warning-color: #ff976a;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.5;
}
.page-container {
padding-bottom: 50px;
}
/* 顶部Banner */
.banner-section {
padding: 0;
height: 180px;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
position: relative;
overflow: visible;
}
.banner-curve {
display: none;
}
.banner-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 20px 0;
height: 100%;
}
.banner-text {
color: white;
z-index: 2;
}
.banner-text h2 {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
}
.banner-text p {
font-size: 14px;
opacity: 0.9;
}
.banner-image {
width: 100px;
height: 100px;
z-index: 2;
}
.banner-image img {
width: 100%;
height: 100%;
object-fit: contain;
}
/* 功能卡片 */
.cards-container {
padding: 20px 16px 16px;
margin-top: -20px;
position: relative;
z-index: 10;
background-color: var(--bg-color);
border-radius: 20px 20px 0 0;
}
.feature-card {
display: flex;
align-items: center;
background: var(--card-bg);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.feature-card:active {
transform: scale(0.98);
}
.card-icon {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 16px;
}
.card-icon .van-icon {
font-size: 24px;
color: white;
}
.submit-icon {
background: linear-gradient(135deg, #1989fa 0%, #39b9f9 100%);
}
.my-icon {
background: linear-gradient(135deg, #07c160 0%, #10d878 100%);
}
.admin-icon {
background: linear-gradient(135deg, #ff6b6b 0%, #ffaa7f 100%);
}
.card-info {
flex: 1;
}
.card-info h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: 4px;
color: var(--text-color);
}
.card-info p {
font-size: 13px;
color: var(--text-light);
margin: 0;
}
.card-arrow {
color: #ccc;
}
/* 使用指南 */
.guide-container {
padding: 0 16px 16px;
}
.guide-header {
margin-bottom: 16px;
}
.guide-header h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-color);
}
.guide-steps {
background: var(--card-bg);
border-radius: 12px;
padding: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.guide-step {
display: flex;
align-items: center;
position: relative;
}
.step-number {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--primary-color);
color: white;
display: flex;
justify-content: center;
align-items: center;
font-weight: 600;
margin-right: 16px;
flex-shrink: 0;
}
.step-content {
flex: 1;
}
.step-content h4 {
font-size: 15px;
font-weight: 600;
margin-bottom: 4px;
color: var(--text-color);
}
.step-content p {
font-size: 13px;
color: var(--text-secondary);
margin: 0;
}
.step-icon {
margin-left: 12px;
color: var(--primary-color);
}
.step-divider {
height: 24px;
width: 1px;
background: #e8e8e8;
margin: 8px 0 8px 15px;
}
/* 常见问题 */
.faq-container {
padding: 0 16px 16px;
}
.faq-header {
margin-bottom: 16px;
display: flex;
align-items: baseline;
}
.faq-header h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-color);
margin-right: 8px;
}
.faq-subtitle {
font-size: 12px;
color: var(--text-light);
}
.faq-list {
background: var(--card-bg);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.faq-item {
border-bottom: 1px solid var(--border-color);
}
.faq-item:last-child {
border-bottom: none;
}
.faq-question {
display: flex;
align-items: center;
padding: 16px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.faq-active .faq-question {
background-color: var(--primary-light);
}
.faq-icon {
color: var(--primary-color);
margin-right: 12px;
}
.faq-arrow {
margin-left: auto;
color: var(--text-light);
transition: transform 0.3s ease;
}
.faq-active .faq-arrow .van-icon {
transform: rotate(180deg);
}
.faq-answer {
padding: 0 16px 16px 44px;
color: var(--text-secondary);
font-size: 13px;
line-height: 1.6;
border-top: 1px dashed var(--border-color);
background-color: rgba(0, 0, 0, 0.02);
}
/* 弹窗样式 */
.popup-title {
text-align: center;
font-size: 16px;
font-weight: 500;
padding: 16px 0;
border-bottom: 1px solid #ebedf0;
}
.popup-content {
padding: 16px;
max-height: calc(100% - 60px);
overflow-y: auto;
}
.suggestion-content {
font-size: 14px;
line-height: 1.5;
}
.suggestion-title {
font-size: 16px;
font-weight: 500;
margin-bottom: 6px;
color: var(--text-color);
}
.suggestion-meta {
display: flex;
justify-content: space-between;
color: var(--text-light);
}
.attachment-list {
display: flex;
flex-wrap: wrap;
}
</style>
<script>
new Vue({
el: '#app',
data: function () {
return {
isAdmin: false, // 控制是否为管理员
allSuggestions: [],
user: 'user1',
activeFaq: null,
totalSuggestions: 0,
respondedPercent: 0,
recentSuggestions: []
}
},
computed: {
userSuggestions: function () {
return this.allSuggestions.filter(function (item) {
return item.user === this.user;
}.bind(this));
}
},
methods: {
goToSubmitPage: function () {
window.location.href = '/platform/suggestionBox/h5/write';
},
goToMyOpinionsPage: function () {
window.location.href = '/platform/suggestionBox/h5/mine';
},
goToAllOpinionsPage: function () {
window.location.href = '/platform/suggestionBox/admin/h5';
},
toggleFaq: function (id) {
this.activeFaq = this.activeFaq === id ? null : id;
}
},
created: function () {
// 初始化数据
this.isAdmin = "${@shiro.hasRole('sysadmin')}" === "true"
}
});
</script>
<!--#
}
#-->
@@ -0,0 +1,549 @@
<!--#layout("/mobile/platform.html"){#-->
<style>
:root {
--primary-color: rgb(0, 78, 100);
--primary-light: rgba(0, 78, 100, 0.1);
--secondary-color: #37A6BD;
--text-color: #333333;
--text-secondary: #666666;
--text-light: #999999;
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--border-color: #eeeeee;
--success-color: #07c160;
--warning-color: #ff976a;
}
#app {
min-height: 100vh;
background-color: var(--bg-color);
display: flex;
flex-direction: column;
}
/* 页面样式 */
.suggestion-list {
padding: 16px;
background-color: var(--bg-color);
flex: 1;
display: flex;
flex-direction: column;
}
.van-pull-refresh, .van-list {
flex: 1;
display: flex;
flex-direction: column;
}
.suggestion-card {
background-color: var(--card-bg);
border-radius: 12px;
margin-bottom: 16px;
padding: 16px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
transition: all 0.2s ease;
border: 1px solid rgba(0, 0, 0, 0.02);
}
.suggestion-card:active {
transform: scale(0.98);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.suggestion-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.suggestion-title {
font-size: 16px;
font-weight: 600;
color: var(--text-color);
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.suggestion-status {
font-size: 12px;
padding: 3px 8px;
border-radius: 12px;
margin-left: 10px;
font-weight: 500;
}
.status-pending {
background-color: #e6f7ff;
color: #1890ff;
}
.status-processing {
background-color: #fff7e6;
color: #fa8c16;
}
.status-completed {
background-color: #f6ffed;
color: #52c41a;
}
.suggestion-content {
font-size: 14px;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 16px;
overflow: hidden;
background-color: var(--bg-color);
padding: 10px;
border-radius: 8px;
word-break: break-all;
white-space: pre-line;
max-height: 3.2em;
text-overflow: ellipsis;
display: block;
}
.suggestion-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 13px;
color: var(--text-light);
border-top: 1px solid #f5f5f5;
padding-top: 12px;
}
.suggestion-time {
display: flex;
align-items: center;
}
.suggestion-time .van-icon {
font-size: 14px;
margin-right: 4px;
}
.suggestion-action {
color: var(--primary-color);
display: flex;
align-items: center;
font-weight: 500;
}
.suggestion-action .van-icon {
font-size: 14px;
margin-left: 2px;
}
.empty-list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 64px 16px;
flex: 1;
}
.empty-icon {
font-size: 64px;
color: #ddd;
margin-bottom: 16px;
text-align: center;
}
.empty-text {
font-size: 15px;
color: var(--text-light);
text-align: center;
margin-bottom: 20px;
}
/* 详情弹窗样式 */
.detail-popup {
padding: 24px;
max-height: 80vh;
overflow-y: auto;
}
.detail-header {
margin-bottom: 20px;
}
.detail-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 10px;
color: var(--text-color);
}
.detail-meta {
display: flex;
justify-content: space-between;
color: var(--text-light);
font-size: 14px;
}
.detail-section {
margin-bottom: 24px;
}
.detail-label {
color: var(--text-secondary);
margin-bottom: 8px;
font-weight: 500;
font-size: 15px;
}
.detail-content {
color: var(--text-color);
line-height: 1.8;
font-size: 15px;
}
.detail-reply {
background-color: #f9f9f9;
padding: 16px;
border-radius: 8px;
border-left: 4px solid var(--primary-color);
}
.detail-attachments {
display: flex;
flex-wrap: wrap;
}
.attachment-item {
width: 90px;
height: 90px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.attachment-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.loader {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
/*!* 自定义导航栏样式 *!*/
/*.van-nav-bar {*/
/* background-color: var(--primary-color);*/
/*}*/
/*.van-nav-bar .van-nav-bar__title {*/
/* color: white;*/
/* font-weight: 500;*/
/*}*/
/*.van-nav-bar .van-icon, .van-nav-bar .van-nav-bar__text {*/
/* color: white;*/
/*}*/
.van-button--primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
/* 下拉刷新和上拉加载样式 */
.van-pull-refresh__track {
flex: 1;
}
.van-list {
min-height: 100%;
}
.no-reply {
padding: 20px 0;
text-align: center;
background-color: var(--bg-color);
border-radius: 8px;
}
.no-reply-icon {
font-size: 36px;
color: #ccc;
margin-bottom: 8px;
}
.no-reply-text {
font-size: 14px;
color: var(--text-light);
}
.submitter-info {
background-color: var(--bg-color);
border-radius: 8px;
padding: 12px 15px;
}
.info-item {
display: flex;
align-items: center;
margin-bottom: 8px;
line-height: 1.6;
}
.info-item:last-child {
margin-bottom: 0;
}
.info-label {
color: var(--text-secondary);
width: 80px;
font-size: 14px;
}
.info-value {
color: var(--text-color);
flex: 1;
font-size: 14px;
}
</style>
<div id="app" v-cloak>
<van-nav-bar title="我的意见建议" left-arrow @click-left="pjaxReplace('/platform/suggestionBox/h5')" fixed placeholder left-text="返回"></van-nav-bar>
<!-- 内容区域 -->
<div class="suggestion-list">
<!-- 下拉刷新和上拉加载更多 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="loadMore"
>
<!-- 空状态 -->
<div class="empty-list" v-if="suggestions.length === 0 && !loading">
<van-icon name="comment-circle-o" class="empty-icon"/>
<div class="empty-text">您还没有提交过意见</div>
<van-button type="primary" size="normal" round @click="goToSubmitPage">去提交意见</van-button>
</div>
<!-- 意见列表 -->
<div class="suggestion-card" v-for="(item, index) in suggestions" :key="item.id"
@click="showDetail(item)">
<div class="suggestion-header">
<div class="suggestion-title">{{ item.title || '意见反馈' }}</div>
<div class="suggestion-status" :class="getStatusClass(item.isReply)">
{{ getStatusText(item.isReply) }}
</div>
</div>
<div class="suggestion-content">{{ item.content }}</div>
<div class="suggestion-footer">
<div class="suggestion-time">
<van-icon name="clock-o"/>
<span>{{ formatDate(item.submitTime) }}</span>
</div>
<div class="suggestion-action">
查看详情
<van-icon name="arrow"/>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
<!-- 详情弹出层 -->
<van-popup v-model="showDetailPopup" round closeable position="bottom" :style="{ height: '80%' }">
<div class="detail-popup" v-if="currentSuggestion">
<div class="detail-header">
<div class="detail-title">{{ currentSuggestion.title || '意见反馈' }}</div>
<div class="detail-meta">
<span>{{ formatDate(currentSuggestion.submitTime) }}</span>
<span :class="getStatusClass(currentSuggestion.isReply)">{{ getStatusText(currentSuggestion.isReply) }}</span>
</div>
</div>
<div class="detail-section">
<div class="detail-label">提交人信息</div>
<div class="submitter-info">
<div class="info-item">
<span class="info-label">姓名:</span>
<span class="info-value">{{ currentSuggestion.submitterName || '未填写' }}</span>
</div>
<div class="info-item">
<span class="info-label">工号:</span>
<span class="info-value">{{ currentSuggestion.submitterLoginName || '未填写' }}</span>
</div>
<div class="info-item">
<span class="info-label">单位:</span>
<span class="info-value">{{ currentSuggestion.submitterUnitName || '未填写' }}</span>
</div>
<div class="info-item">
<span class="info-label">联系方式:</span>
<span class="info-value">{{ currentSuggestion.concat || '未填写' }}</span>
</div>
</div>
</div>
<div class="detail-section">
<div class="detail-label">意见内容</div>
<div class="detail-content">{{ currentSuggestion.content }}</div>
</div>
<div class="detail-section"
v-if="currentSuggestion.attachments && currentSuggestion.attachments.length > 0">
<div class="detail-label">附件</div>
<div class="detail-attachments">
<vant-file-upload :del="false" :files.sync="currentSuggestion.attachments"
view></vant-file-upload>
<!-- <div class="attachment-item" v-for="(file, idx) in currentSuggestion.attachments" :key="idx"
@click.stop="previewImage(file.url, idx)">
<img :src="file.url" class="attachment-image">
</div>-->
</div>
</div>
<div class="detail-section" v-if="currentSuggestion.isReply">
<div class="detail-label">回复</div>
<div class="detail-reply detail-content">{{ currentSuggestion.replyContent }}</div>
</div>
<div v-else class="detail-section">
<div class="detail-label">回复</div>
<div class="no-reply">
<van-icon name="chat-o" class="no-reply-icon"/>
<div class="no-reply-text">暂无回复</div>
</div>
</div>
</div>
</van-popup>
</div>
<script>
const vue = new Vue({
el: '#app',
data() {
return {
refreshing: false,
loading: false,
finished: false,
showDetailPopup: false,
currentSuggestion: null,
pageNumber: 1,
pageSize: 10,
suggestions: []
}
},
created() {
this.loadData();
},
methods: {
loadData() {
this.loading = true;
$.post('/platform/suggestionBox/pageData', {
pageNumber: this.pageNumber,
pageSize: this.pageSize
}).done((res) => {
if (res.code === 0 && res.data) {
if (this.pageNumber === 1) {
this.suggestions = res.data.list || [];
} else {
this.suggestions = this.suggestions.concat(res.data.list || []);
}
this.finished = !res.data.list || res.data.list.length < this.pageSize;
} else {
this.finished = true;
}
this.loading = false;
this.refreshing = false;
}).fail(() => {
this.loading = false;
this.refreshing = false;
this.finished = true;
});
},
// 下拉刷新
onRefresh() {
this.pageNumber = 1;
this.finished = false;
this.loadData();
},
// 上拉加载更多
loadMore() {
this.pageNumber++;
this.loadData();
},
// 查看详情
showDetail(suggestion) {
if (suggestion.attachments && typeof suggestion.attachments === 'string') {
try {
suggestion.attachments = JSON.parse(suggestion.attachments);
} catch (e) {
suggestion.attachments = [];
}
}
this.currentSuggestion = suggestion;
this.showDetailPopup = true;
},
// 获取状态class
getStatusClass(isReply) {
if (!isReply || isReply === 0) return 'status-pending';
if (isReply === 1) return 'status-processing';
return 'status-completed';
},
// 获取状态文本
getStatusText(isReply) {
if (!isReply || isReply === 0) return '待处理';
if (isReply === 1) return '处理中';
return '已处理';
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0');
},
// 前往提交意见页面
goToSubmitPage() {
pjaxReplace("/platform/suggestionBox/h5/write")
},
// 预览图片
previewImage(url, index) {
if (!url) return;
// 创建图片查看器
const urls = this.currentSuggestion.attachments.map(file => file.url);
this.$imagePreview({
images: urls,
startPosition: index
});
}
}
});
window.addEventListener('pageshow', e => {
if (e.persisted || (window.performance && window.performance.navigation.type === 2)) {
vue.loadData()
}
})
</script>
<!--#}#-->
@@ -0,0 +1,297 @@
<!--#layout("/mobile/platform.html"){#-->
<style>
.form-container {
background-color: #f6f6f6;
padding: 0;
}
.form-section {
margin-bottom: 12px;
}
.section-title {
display: flex;
align-items: center;
padding: 12px 16px;
background: #fff;
border-bottom: 1px solid #f6f6f6;
}
.dot {
width: 8px;
height: 8px;
background-color: rgb(0, 78, 100);
border-radius: 50%;
margin-right: 8px;
}
.section-title span {
color: #333;
font-weight: 500;
}
.input-row {
display: flex;
align-items: center;
border-bottom: 1px solid #f5f5f5;
padding: 12px 16px;
background: #fff;
}
.input-label {
width: 80px;
color: #333;
padding: 8px 8px 8px 0;
}
.input-control {
flex: 1;
text-align: right;
}
.input-control input {
width: 100%;
border: none;
outline: none;
text-align: right;
color: #666;
font-size: 14px;
}
.textarea-container {
padding: 10px 16px;
background: #fff;
border-bottom: 1px solid #f5f5f5;
}
.textarea-container textarea {
width: 100%;
height: 120px;
border: none;
outline: none;
resize: none;
font-size: 14px;
color: #333;
}
.word-count {
text-align: right;
font-size: 12px;
color: #999;
margin-top: 4px;
}
.upload-area {
padding: 16px;
background: #fff;
}
.upload-grid {
display: flex;
flex-wrap: wrap;
}
.upload-item, .upload-btn {
width: 80px;
height: 80px;
margin-right: 8px;
margin-bottom: 8px;
border-radius: 4px;
overflow: hidden;
position: relative;
}
.upload-btn {
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
color: #999;
}
.uploaded-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.delete-btn {
position: absolute;
top: 0;
right: 0;
width: 20px;
height: 20px;
background: rgba(0, 0, 0, 0.5);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0 0 0 4px;
}
.submit-area {
padding: 20px 16px;
}
</style>
<div id="app" v-cloak>
<van-sticky>
<van-nav-bar title="我要提意见" left-arrow left-text="返回"
@click-left="pjaxReplace('/platform/suggestionBox/h5')"></van-nav-bar>
</van-sticky>
<div class="form-container">
<div class="form-section">
<div class="section-title">
<div class="dot"></div>
<span>填写人信息</span>
</div>
<div class="input-row">
<div class="input-label">姓名</div>
<div class="input-control">
<input type="text" v-model="formData.submitterName" placeholder="请输入姓名" readonly>
</div>
</div>
<div class="input-row">
<div class="input-label">工号</div>
<div class="input-control">
<input type="text" v-model="formData.submitterLoginName" placeholder="请输入工号" readonly>
</div>
</div>
<div class="input-row">
<div class="input-label">手机号码</div>
<div class="input-control">
<input type="tel" v-model="formData.concat" placeholder="请输入手机号码">
</div>
</div>
<div class="input-row">
<div class="input-label">所在单位</div>
<div class="input-control">
<input type="text" v-model="formData.submitterUnitName" placeholder="请输入所在单位" readonly>
</div>
</div>
</div>
<div class="form-section">
<div class="section-title">
<div class="dot"></div>
<span>填写标题</span>
</div>
<div class="input-row">
<div class="input-label">标题</div>
<div class="input-control">
<input type="text" v-model="formData.title" placeholder="请输入意见标题">
</div>
</div>
</div>
<div class="form-section">
<div class="section-title">
<div class="dot"></div>
<span>填写意见建议内容</span>
</div>
<div class="textarea-container">
<textarea v-model="formData.content" placeholder="请描述您要填写的意见建议内容..."></textarea>
</div>
</div>
<div class="form-section">
<div class="section-title">
<div class="dot"></div>
<span>照片上传</span>
</div>
<div class="upload-area">
<vant-file-upload :files.sync="formData.attachments" :max="15"></vant-file-upload>
<!-- <div class="upload-grid">-->
<!-- <div class="upload-item" v-for="(item, index) in formData.fileList" :key="index">-->
<!-- <img :src="item.content || item.url" class="uploaded-image">-->
<!-- <div class="delete-btn" @click="deleteImage(index)">-->
<!-- <van-icon name="cross" />-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="upload-btn" v-if="formData.fileList.length < 3" @click="triggerUpload">-->
<!-- <input type="file" ref="fileInput" style="display:none" accept="image/*" @change="onFileChange" multiple>-->
<!-- <van-icon name="plus" size="24" />-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
<div class="submit-area">
<van-button @click="submitForm" style="border-radius: 10px" block type="info" :color="themeColor">
提 交
</van-button>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
mixins: [mobileMixins],
data() {
return {
formData: {
name: '',
mobile: '',
idCard: '',
department: '',
content: '',
address: '',
fileList: []
}
}
},
methods: {
goBack() {
history.back();
},
async submitForm() {
if (!this.formData.concat.trim()) {
this.$toast('请输入手机号码');
return;
}
//正则验证
if (!/^1[3-9]\d{9}$/.test(this.formData.concat)) {
this.$toast('请输入正确的手机号码');
return;
}
if (!this.formData.content.trim()) {
this.$toast('请输入投诉内容');
return;
}
this.$dialog.confirm({
title: '提示',
message: '您确定要提交吗?'
}).then(async () => {
const {
code,
data,
msg
} = await $.post('/platform/suggestionBox/submit', {suggestion: JSON.stringify(this.formData)})
if (code === 0) {
this.$toast.success('提交成功')
setTimeout(() => {
pjaxReplace("/platform/suggestionBox/h5/mine")
}, 200)
} else {
this.$toast(msg)
}
})
}
},
created() {
const id = GetQueryString("id")
if (!id) {
this.$set(this.formData, 'submitterId', "${@shiro.getPrincipalProperty('id')}")
this.$set(this.formData, 'submitterName', "${@shiro.getPrincipalProperty('username')}")
this.$set(this.formData, 'submitterLoginName', "${@shiro.getPrincipalProperty('loginname')}")
this.$set(this.formData, 'submitterUnitId', "${@shiro.getPrincipalProperty('unitid')}")
this.$set(this.formData, 'submitterUnitName', "${@shiro.getPrincipalProperty('unit').getName()}")
this.$set(this.formData, 'submitterUnionId', "${@shiro.getPrincipalProperty('union').getId()}")
this.$set(this.formData, 'submitterUnionName', "${@shiro.getPrincipalProperty('union').getUnionname()}")
this.$set(this.formData, 'concat', "${@shiro.getPrincipalProperty('mobile')}")
console.log(this.formData)
}
}
})
</script>
<!--#}#-->
@@ -406,15 +406,19 @@ layout("/layouts/platform.html"){
this.tableLoading = false
if (data.code == 0) {
data.data.list.forEach(v => {
const a = JSON.parse(v.sendTypes).map(z => {
if (z == "msg") {
return "短信"
} else {
return "微信"
}
if (v.sendTypes){
const a = JSON.parse(v.sendTypes).map(z => {
if (z === "DingTalk") {
return "钉钉"
} else {
return "钉钉"
}
})
v.sendTypesName = a.toString()
})
v.sendTypesName = a.toString()
} else {
v.sendTypesName = "钉钉"
}
})
this.tableData = data.data.list;
this.pageForm.totalCount = data.data.totalCount;
@@ -214,6 +214,9 @@ layout("/layouts/platform.html"){
:visible.sync="dialogVisible"
width="50%">
<el-timeline>
<el-timeline-item timestamp="更新规则" placement="top">
<div style="color:red;"> 更新人员不会改变是否会员数据,只会改变基础数据。会员是管理员邀请入会,在【数据更新记录】中新入职的人员会自动发送钉钉邀请入会</div>
</el-timeline-item>
<el-timeline-item timestamp="数据源" placement="top">
<el-radio-group class="checkGroup" v-model="sourceTime" style="width: 100%">
<el-row v-for="item in latelyUpdateTimes" style="margin-bottom: 10px">
@@ -24,13 +24,6 @@ layout("/layouts/platform.html"){
</div>
<div class="btn-group tool-button mt5 mr10">
<el-checkbox-group v-model="pageForm.sendTypes" @change="doSearch">
<el-checkbox-button label="msg" border>短信发送</el-checkbox-button>
<el-checkbox-button label="WeChat" border>微信发送</el-checkbox-button>
</el-checkbox-group>
</div>
<div class="btn-group tool-button ">
<el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
</div>
@@ -77,7 +70,8 @@ layout("/layouts/platform.html"){
<template scope="{row}">
<el-button @click="doSend(row)" size="mini" type="success" v-if="!row.hold">发送</el-button>
<el-button @click="openView(row)" size="mini" v-if="row.hold">查看</el-button>
<el-button size="mini" type="primary" @click="openEdit(row)" v-if="!row.hold">编辑</el-button>
<el-button size="mini" type="primary" @click="openEdit(row)" v-if="!row.hold">编辑
</el-button>
<el-button size="mini" type="danger" @click="doDelete(row.id)">删除</el-button>
</template>
@@ -101,7 +95,7 @@ layout("/layouts/platform.html"){
sendTypes: [],
},
tableColumns: [
{prop: 'title', label: '活动名称'},
{prop: 'title', label: '标题'},
// {prop: 'module', label: '所属模块'},
// {prop: 'teacherMeetingName', label: '所属教代会'},
{prop: 'type', label: '人员范围', sortable: true},
@@ -167,17 +161,21 @@ layout("/layouts/platform.html"){
$.post(loc() + "/pageData", pageForm, (data) => {
sublime.closeLoadingbar();
this.tableLoading = false
if (data.code == 0) {
if (data.code === 0) {
data.data.list.forEach(v => {
const a = JSON.parse(v.sendTypes).map(z => {
if (z == "msg") {
return "短信"
} else {
return "微信"
}
if (v.sendTypes) {
const a = JSON.parse(v.sendTypes).map(z => {
if (z === "DingTalk") {
return "钉钉"
} else {
return "钉钉"
}
})
v.sendTypesName = a.toString()
})
v.sendTypesName = a.toString()
} else {
v.sendTypesName = "钉钉"
}
})
this.tableData = data.data.list;
this.pageForm.totalCount = data.data.totalCount;
@@ -43,7 +43,7 @@ layout("/layouts/platform.html"){
</el-row>
</template>
<div>
<msg-notify :hold="hold" :notify_id="notify_id" ref="a"
<msg-notify :hold="hold" :notify_id="notify_id" ref="msgNotifyRef"
v-model="msgData"></msg-notify>
</div>
@@ -97,11 +97,6 @@ layout("/layouts/platform.html"){
pageSize: 5,
totalCount: 0,
},
formRules: {
title: [{required: true, message: '请输入发送标题', trigger: ['blur', 'change']}],
sendTypes: [{required: true, message: '请输入发送方式', trigger: ['blur', 'change']}],
content: [{required: true, message: '请输入发送内容', trigger: ['blur', 'change']}],
}
}
},
components: {
@@ -155,6 +150,7 @@ layout("/layouts/platform.html"){
this.selectDialogVisible = true
},
async doAdd(flag) {
await this.$refs.msgNotifyRef.$refs.addform.validate()
this.formData = this.msgData
if (this.formData.sendMode === 'four' && !this.formData.existsLoginNameRedisKey) {
this.$notify.warning('请先上传文件核对人员在选择发送!')
@@ -35,6 +35,9 @@ layout("/layouts/platform.html"){
style="width: 80px;">
<el-option label="姓名" value="u.username"></el-option>
<el-option label="工号" value="u.loginname"></el-option>
<el-option label="模块" value="mnu.apiModule"></el-option>
<el-option label="标题" value="mnu.title"></el-option>
<el-option label="内容" value="mnu.content"></el-option>
</el-select>
</el-input>
@@ -57,12 +60,6 @@ layout("/layouts/platform.html"){
</el-select>
</div>
<div class="btn-group tool-button mt5 mr10">
<el-checkbox-group v-model="pageForm.sendTypes" @change="doSearch">
<el-checkbox-button label="msg" border>短信发送</el-checkbox-button>
<el-checkbox-button label="WeChat" border>微信发送</el-checkbox-button>
</el-checkbox-group>
</div>
<div class="btn-group tool-button ">
<el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
</div>
@@ -112,10 +109,11 @@ layout("/layouts/platform.html"){
{prop: 'username', label: '姓名'},
{prop: 'loginname', label: '工号'},
{prop: 'unitname', label: '单位', sortable: true},
{prop: 'unionname', label: '工会', sortable: true},
{prop: 'title', label: '所属活动'},
{prop: 'title', label: '标题'},
{prop: 'sendTypesName', label: '发送类型'},
{prop: 'apiModule', label: '发送模块'},
{prop: 'content', label: '消息内容'},
{prop: 'link', label: '发送链接'},
{prop: 'sendTime', label: '发送时间', sortable: true},
]
}
@@ -145,17 +143,17 @@ layout("/layouts/platform.html"){
sublime.closeLoadingbar();
this.tableLoading = false
if (data.code == 0) {
console.log(data.data.list)
data.data.list.forEach(v => {
const a = JSON.parse(v.sendTypes).map(z => {
if (z == "msg") {
return "短信"
} else {
return "微信"
}
})
v.sendTypesName = a.toString()
if (v.sendTypes) {
const a = JSON.parse(v.sendTypes).map(z => {
if (z === "DingTalk") {
return "钉钉"
}
})
v.sendTypesName = a.toString()
} else {
v.sendTypesName = "钉钉"
}
})
this.tableData = data.data.list;
this.pageForm.totalCount = data.data.totalCount;
@@ -169,6 +167,7 @@ layout("/layouts/platform.html"){
this.pageData();
this.unions = await getUnions()
this.flushUnits()
await this.getTitleList(null)
}
})
@@ -177,4 +176,4 @@ layout("/layouts/platform.html"){
<!--#
}
#-->
#-->
@@ -87,7 +87,7 @@ layout("/layouts/platform.html"){
</template>
<template #view>
<proposal-info ref="info"></proposal-info>
<proposal-info ref="infoRef"></proposal-info>
</template>
</guava>
</div>
@@ -140,7 +140,7 @@ layout("/layouts/platform.html"){
},
components: {
'guava': httpVueLoader('/components/plugins/Guava.vue'),
'proposal-info': httpVueLoader('/components/proposal/ProposalInfo.vue?v=1.0.1'),
'proposal-info': httpVueLoader('/components/proposal/ProposalInfo.vue?v=' + new Date().getTime()),
'proposal-table': httpVueLoader('/components/proposal/ProposalTable.vue'),
},
methods: {
@@ -192,7 +192,7 @@ layout("/layouts/platform.html"){
},
openView(row) {
this.$refs.guava.view()
this.$refs.info.openView(row.id)
this.$refs.infoRef.openView(row.id)
},
pageData() {
sublime.showLoadingbar();
@@ -83,63 +83,45 @@ layout("/layouts/platform.html"){
</template>
<template #edit>
<proposal-info ref="audit"></proposal-info>
<el-form style="margin-top: 20px" :model="formData" ref="auditForm" :rules="formRules" label-width="120px">
<!-- <vi-title :title="proTableData.length > 1 ? '并案信息' : '提案信息'"></vi-title>-->
<!-- <el-form-item>-->
<!-- <el-table :data="proTableData" tooltip-effect="dark"-->
<!-- style="width: 100%">-->
<!-- <el-table-column align="left" header-align="left" prop="proposalCode"-->
<!-- label="提案编号"></el-table-column>-->
<!-- <el-table-column align="left" header-align="left" prop="username"-->
<!-- label="提案人"-->
<!-- show-overflow-tooltip></el-table-column>-->
<!-- <el-table-column align="left" header-align="left" prop="proposalName" label="提案名称"-->
<!-- show-overflow-tooltip>-->
<!-- <template slot-scope="{row}">-->
<!-- <span @click="openView(row)"-->
<!-- style="color: #236eb4; cursor: pointer; text-decoration: underline">{{row.proposalName}}</span>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- </el-form-item>-->
<vi-title title="分管校领导审批信息"></vi-title>
<div style="text-align: center;margin-top: -40px;"
v-if="formData.auditUnderTakeList && formData.auditUnderTakeList.length>1">
<span class="text-danger">温馨提醒:该提案的承办单位【{{formData.auditUnderTakeName}}】是由您来分管的,只需要审批一次即可。</span>
</div>
<el-row gutter="20">
<el-col :span="12">
<el-form-item prop="username" label="审核人">
<el-input v-model="formData.username" readonly></el-input>
<proposal-info ref="audit" label="分管校领导审批" handle>
<template #handle>
<el-form style="margin-top: 20px" :model="formData" ref="auditForm" :rules="formRules" label-width="120px">
<div style="text-align: center;margin-top: -40px;"
v-if="formData.auditUnderTakeList && formData.auditUnderTakeList.length>1">
<span class="text-danger">温馨提醒:该提案的承办单位【{{formData.auditUnderTakeName}}】是由您来分管的,只需要审批一次即可。</span>
</div>
<el-row gutter="20">
<el-col :span="12">
<el-form-item prop="username" label="审核人">
<el-input v-model="formData.username" readonly></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="auditTime" label="审核时间">
<el-input v-model="formData.auditTime" readonly></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="审批意见" prop="opinion">
<el-input type="textarea" v-model="formData.opinion" rows="4" maxlength="1000"
placeholder="请填写您的审批意见"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="auditTime" label="审核时间">
<el-input v-model="formData.auditTime" readonly></el-input>
<el-form-item v-if="isSign()" prop="sign" label="签&emsp;&emsp;字">
<sign prefix="proposal" :qz.sync="formData.sign"></sign>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="审批意见" prop="opinion">
<el-input type="textarea" v-model="formData.opinion" rows="4" maxlength="1000"
placeholder="请填写您的审批意见"></el-input>
</el-form-item>
<el-form-item v-if="isSign()" prop="sign" label="签&emsp;&emsp;字">
<sign prefix="proposal" :qz.sync="formData.sign"></sign>
</el-form-item>
<el-row style="margin: 40px 0;text-align: right">
<el-button @click="$refs.guava.index()">
取消
</el-button>
<el-button type="primary" :disabled="submitDisabled" @click="doAudit(true)">
提交
</el-button>
</el-row>
<el-row style="margin: 40px 0;text-align: right">
<el-button @click="$refs.guava.index()">
取消
</el-button>
<el-button type="primary" :disabled="submitDisabled" @click="doAudit(true)">
提交
</el-button>
</el-row>
</el-form>
</el-form>
</template>
</proposal-info>
</template>
<template #view>
@@ -133,7 +133,6 @@ layout("/layouts/platform.html"){
<proposal-info ref="audit" label="提案工作组立案" handle>
<template #handle>
<el-form :model="formData" ref="auditForm" :rules="formRules" label-width="120px">
<vi-title title="审核信息"></vi-title>
<el-row gutter="20">
<el-col :span="12">
<el-form-item prop="username" label="审核人">
@@ -109,7 +109,6 @@ layout("/layouts/platform.html"){
</el-table>
</el-form-item>
</div>
<vi-title title="审核信息"></vi-title>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item prop="username" label="审核人">
@@ -64,7 +64,6 @@ layout("/layouts/platform.html"){
<proposal-info ref="audit" label="团长审核" handle>
<template #handle>
<el-form :model="formData" ref="auditForm" :rules="formRules" label-width="120px">
<vi-title title="审核信息"></vi-title>
<el-row gutter="20">
<el-col :span="12">
<el-form-item prop="username" label="审核人">
@@ -79,7 +79,6 @@ layout("/layouts/platform.html"){
<proposal-info ref="audit" label="反馈评价" handle>
<template #handle>
<el-form :model="formData" ref="auditForm" :rules="formRules" label-width="150px">
<vi-title title="反馈评价信息"></vi-title>
<el-row gutter="20">
<el-col :span="12">
<el-form-item prop="username" label="反馈人">
@@ -119,7 +119,6 @@ layout("/layouts/platform.html"){
<proposal-info ref="audit" label="承办单位答复" handle>
<template #handle>
<el-form :model="formData" ref="auditForm" :rules="formRules" label-width="120px">
<vi-title title="承办单位答复"></vi-title>
<el-row gutter="20">
<el-col :span="24">
<el-form-item prop="username" label="答复单位">
@@ -73,7 +73,6 @@ layout("/layouts/platform.html"){
<proposal-info ref="audit" label="附议提案" handle>
<template #handle>
<el-form :model="formData" ref="auditForm" :rules="formRules" label-width="120px">
<vi-title title="附议信息"></vi-title>
<el-row gutter="20">
<el-col :span="12">
<el-form-item prop="username" label="附议人">
@@ -91,7 +91,6 @@ layout("/layouts/platform.html"){
<proposal-info ref="audit" label="承办单位提出意见" handle>
<template #handle>
<el-form :model="formData" ref="auditForm" :rules="formRules" label-width="120px">
<vi-title title="审核信息"></vi-title>
<el-form-item label="承办单位">
<el-input v-model="formData.underTakeName" readonly></el-input>
</el-form-item>