Merge remote-tracking branch 'origin/feature_职代会' into feature_职代会UI优化

# Conflicts:
#	src/main/resources/views/layouts/platform_h5.html
This commit is contained in:
2026-08-29 10:16:55 +08:00
48 changed files with 8172 additions and 1 deletions
@@ -0,0 +1,170 @@
package com.budwk.app.zhgh.democratic.congress.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.democratic.congress.service.CongressManageService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.util.Date;
@IocBean
@At("/platform/congress/materials/manage")
@Ok("json:full")
public class CongressMaterialsManageController {
@Inject
private Dao dao;
@Inject
private CongressManageService congressManageService;
@At("/index")
@Ok("beetl:/platform/zhgh/democratic/congress/materials/manage/index.html")
@SaCheckPermission("congress.materials.manage")
public void index() {
}
@At("/sessions")
@SaCheckPermission("congress.materials.manage")
public Result sessions() {
return Result.success(congressManageService.sessionOptions());
}
@At("/types")
@SaCheckPermission("congress.materials.manage")
public Result types() {
return Result.success(congressManageService.typeOptions());
}
@At("/pageData")
@SaCheckPermission("congress.materials.manage")
public Result materialsPageData(PageForm pageForm,
@Param("sessionId") String sessionId,
@Param("typeId") String typeId,
@Param("name") String name) {
StringBuilder text = new StringBuilder("""
SELECT
m.id,
m.name,
m.session_id AS sessionId,
m.time_id AS timeId,
m.type_id AS typeId,
m.sort_no AS sortNo,
m.file_name AS fileName,
m.file_url AS fileUrl,
m.file_type AS fileType,
m.file_size AS fileSize,
m.create_time AS createTime,
mt.name AS typeName,
tcs.fullName AS sessionName
FROM congress_materials m
LEFT JOIN congress_materials_type mt ON mt.id = m.type_id
LEFT JOIN teacher_congress_session tcs ON tcs.id = m.session_id
WHERE COALESCE(m.deleted, 0) = 0
""");
NutMap params = NutMap.NEW();
congressManageService.appendEquals(text, params, "m.session_id", "sessionId", sessionId);
congressManageService.appendEquals(text, params, "m.type_id", "typeId", typeId);
if (StrUtil.isNotBlank(name)) {
text.append(" AND m.name LIKE @name");
params.addv("name", "%" + name.trim() + "%");
}
text.append(" ORDER BY m.sort_no, m.create_time DESC, m.id DESC");
return congressManageService.page(pageForm, text.toString(), params);
}
@At("/get")
@SaCheckPermission("congress.materials.manage")
public Result materialsGet(@Param("id") String id) {
return Result.success(congressManageService.fetchMap("""
SELECT
id,
name,
session_id AS sessionId,
time_id AS timeId,
type_id AS typeId,
sort_no AS sortNo,
file_name AS fileName,
file_url AS fileUrl,
file_type AS fileType,
file_size AS fileSize
FROM congress_materials
WHERE id = @id
AND COALESCE(deleted, 0) = 0
""", NutMap.NEW().addv("id", id)));
}
@At("/save")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("congress.materials.manage")
@SLog(tag = "职代会-会议资料", msg = "保存会议资料")
public Result materialsSave(@Param("id") String id,
@Param("name") String name,
@Param("sessionId") String sessionId,
@Param("typeId") String typeId,
@Param("sortNo") Integer sortNo,
@Param("fileName") String fileName,
@Param("fileUrl") String fileUrl,
@Param("fileType") String fileType,
@Param("fileSize") String fileSize) {
if (StrUtil.hasBlank(name, sessionId, typeId, fileUrl)) {
return Result.error("届次、资料名称、资料类型和文件不能为空");
}
Date now = new Date();
Chain chain = Chain.make("name", name.trim())
.add("session_id", sessionId)
.add("time_id", null)
.add("type_id", typeId)
.add("sort_no", sortNo == null ? 0 : sortNo)
.add("file_name", StrUtil.blankToDefault(fileName, name.trim()))
.add("file_url", fileUrl)
.add("file_type", congressManageService.normalizeFileType(fileType, fileName, fileUrl))
.add("file_size", StrUtil.blankToDefault(fileSize, null))
.add("update_time", now);
if (StrUtil.isBlank(id)) {
String newId = R.UU32();
chain.add("id", newId)
.add("create_time", now)
.add("deleted", 0);
dao.insert("congress_materials", chain);
return Result.success().addData(newId);
}
dao.update("congress_materials", chain,
Cnd.where("id", "=", id).and("deleted", "=", 0));
return Result.success().addData(id);
}
@At("/delete")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("congress.materials.manage")
@SLog(tag = "职代会-会议资料", msg = "删除会议资料")
public Result materialsDelete(@Param("id") String id) {
if (congressManageService.count("""
SELECT COUNT(1)
FROM congress_voting_issue
WHERE materials_id = @id
AND COALESCE(deleted, 0) = 0
""", NutMap.NEW().addv("id", id)) > 0) {
return Result.error("该资料已关联投票议题,不能删除");
}
congressManageService.softDelete("congress_materials", id);
return Result.success();
}
}
@@ -0,0 +1,132 @@
package com.budwk.app.zhgh.democratic.congress.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.democratic.congress.service.CongressManageService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.util.Date;
@IocBean
@At("/platform/congress/materials/type")
@Ok("json:full")
public class CongressMaterialsTypeController {
@Inject
private Dao dao;
@Inject
private CongressManageService congressManageService;
@At("/index")
@Ok("beetl:/platform/zhgh/democratic/congress/materials/type/index.html")
@SaCheckPermission("congress.materials.type")
public void index() {
}
@At("/pageData")
@SaCheckPermission("congress.materials.type")
public Result typePageData(PageForm pageForm,
@Param("name") String name,
@Param("isVote") String isVote) {
StringBuilder text = new StringBuilder("""
SELECT id, name, is_vote AS isVote, sort_no AS sortNo, create_time AS createTime
FROM congress_materials_type
WHERE COALESCE(deleted, 0) = 0
""");
NutMap params = NutMap.NEW();
if (StrUtil.isNotBlank(name)) {
text.append(" AND name LIKE @name");
params.addv("name", "%" + name.trim() + "%");
}
if (StrUtil.isNotBlank(isVote)) {
text.append(" AND is_vote = @isVote");
params.addv("isVote", "1".equals(isVote) || "true".equalsIgnoreCase(isVote) ? 1 : 0);
}
text.append(" ORDER BY sort_no, create_time, id");
return congressManageService.page(pageForm, text.toString(), params);
}
@At("/get")
@SaCheckPermission("congress.materials.type")
public Result typeGet(@Param("id") String id) {
return Result.success(congressManageService.fetchMap("""
SELECT id, name, is_vote AS isVote, sort_no AS sortNo
FROM congress_materials_type
WHERE id = @id
AND COALESCE(deleted, 0) = 0
""", NutMap.NEW().addv("id", id)));
}
@At("/save")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("congress.materials.type")
@SLog(tag = "职代会-资料类型", msg = "保存资料类型")
public Result typeSave(@Param("id") String id,
@Param("name") String name,
@Param("isVote") boolean isVote,
@Param("sortNo") Integer sortNo) {
if (StrUtil.isBlank(name)) {
return Result.error("资料类型不能为空");
}
int repeat = congressManageService.count("""
SELECT COUNT(1)
FROM congress_materials_type
WHERE name = @name
AND COALESCE(deleted, 0) = 0
AND (@id IS NULL OR id <> @id)
""", NutMap.NEW().addv("name", name.trim()).addv("id", StrUtil.blankToDefault(id, null)));
if (repeat > 0) {
return Result.error("资料类型已存在");
}
Date now = new Date();
Chain chain = Chain.make("name", name.trim())
.add("is_vote", isVote ? 1 : 0)
.add("sort_no", sortNo == null ? 0 : sortNo)
.add("update_time", now);
if (StrUtil.isBlank(id)) {
chain.add("id", R.UU32())
.add("create_time", now)
.add("deleted", 0);
dao.insert("congress_materials_type", chain);
} else {
dao.update("congress_materials_type", chain,
Cnd.where("id", "=", id).and("deleted", "=", 0));
}
return Result.success();
}
@At("/delete")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("congress.materials.type")
@SLog(tag = "职代会-资料类型", msg = "删除资料类型")
public Result typeDelete(@Param("id") String id) {
if (congressManageService.count("""
SELECT COUNT(1)
FROM congress_materials
WHERE type_id = @id
AND COALESCE(deleted, 0) = 0
""", NutMap.NEW().addv("id", id)) > 0) {
return Result.error("该类型下已有资料,不能删除");
}
congressManageService.softDelete("congress_materials_type", id);
return Result.success();
}
}
@@ -0,0 +1,92 @@
package com.budwk.app.zhgh.democratic.congress.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.democratic.congress.service.CongressManageService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.util.Date;
@IocBean
@At("/platform/congress/record")
@Ok("json:full")
public class CongressRecordController {
@Inject
private Dao dao;
@Inject
private CongressManageService congressManageService;
@At("/index")
@Ok("beetl:/platform/zhgh/democratic/congress/record/index.html")
@SaCheckPermission("congress.signIn.record")
public void index() {
}
@At("/sessions")
@SaCheckPermission("congress.signIn.record")
public Result sessions() {
return Result.success(congressManageService.sessionOptions());
}
@At("/meetings")
@SaCheckPermission("congress.signIn.record")
public Result meetings(@Param("sessionId") String sessionId) {
return Result.success(congressManageService.meetingOptions(sessionId));
}
@At("/pageData")
@SaCheckPermission("congress.signIn.record")
public Result recordPageData(PageForm pageForm,
@Param("sessionId") String sessionId,
@Param("meetingId") String meetingId,
@Param("keyword") String keyword) {
StringBuilder text = new StringBuilder("""
SELECT
si.id,
si.emplid,
si.name,
si.create_time AS signInTime,
si.meeting_id AS meetingId,
si.time_id AS timeId,
m.meeting_name AS meetingName,
m.sign_in_start_time AS signInStartTime,
m.sign_in_end_time AS signInEndTime,
tcs.fullName AS sessionName,
COALESCE(r.delegation_name, d.name) AS delegationName
FROM congress_sign_in si
LEFT JOIN congress_sign_in_meeting m ON m.id = si.meeting_id
LEFT JOIN teacher_congress_session tcs ON tcs.id = si.session_id AND tcs.delFlag = 0
LEFT JOIN congress_rep r ON r.id = si.rep_id
LEFT JOIN congress_delegation d ON d.id = si.delegation_id
WHERE COALESCE(si.deleted, 0) = 0
""");
NutMap params = NutMap.NEW();
congressManageService.appendEquals(text, params, "si.session_id", "sessionId", sessionId);
congressManageService.appendEquals(text, params, "si.meeting_id", "meetingId", meetingId);
if (StrUtil.isNotBlank(keyword)) {
text.append(" AND (si.emplid LIKE @keyword OR si.name LIKE @keyword)");
params.addv("keyword", "%" + keyword.trim() + "%");
}
text.append(" ORDER BY si.create_time DESC, si.id DESC");
return congressManageService.page(pageForm, text.toString(), params);
}
}
@@ -0,0 +1,155 @@
package com.budwk.app.zhgh.democratic.congress.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.democratic.congress.service.CongressManageService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.util.Date;
@IocBean
@At("/platform/congress/signIn")
@Ok("json:full")
public class CongressSignInController {
@Inject
private Dao dao;
@Inject
private CongressManageService congressManageService;
@At("/index")
@Ok("beetl:/platform/zhgh/democratic/congress/signIn/index.html")
@SaCheckPermission("congress.signIn.manage")
public void index() {
}
@At("/sessions")
@SaCheckPermission("congress.signIn.manage")
public Result sessions() {
return Result.success(congressManageService.sessionOptions());
}
@At("/pageData")
@SaCheckPermission("congress.signIn.manage")
public Result signInPageData(PageForm pageForm, @Param("sessionId") String sessionId) {
StringBuilder text = new StringBuilder("""
SELECT
m.id,
m.meeting_name AS meetingName,
m.sign_in_start_time AS signInStartTime,
m.sign_in_end_time AS signInEndTime,
m.session_id AS sessionId,
m.time_id AS timeId,
m.remark,
tcs.fullName AS sessionName,
(
SELECT COUNT(1)
FROM congress_sign_in si
WHERE si.meeting_id = m.id
AND COALESCE(si.deleted, 0) = 0
) AS signInCount
FROM congress_sign_in_meeting m
LEFT JOIN teacher_congress_session tcs ON tcs.id = m.session_id AND tcs.delFlag = 0
WHERE COALESCE(m.deleted, 0) = 0
""");
NutMap params = NutMap.NEW();
congressManageService.appendEquals(text, params, "m.session_id", "sessionId", sessionId);
text.append(" ORDER BY m.sign_in_start_time DESC, m.create_time DESC, m.id DESC");
return congressManageService.page(pageForm, text.toString(), params);
}
@At("/get")
@SaCheckPermission("congress.signIn.manage")
public Result signInGet(@Param("id") String id) {
return Result.success(congressManageService.fetchMap("""
SELECT
id,
meeting_name AS meetingName,
sign_in_start_time AS signInStartTime,
sign_in_end_time AS signInEndTime,
session_id AS sessionId,
time_id AS timeId,
remark
FROM congress_sign_in_meeting
WHERE id = @id
AND COALESCE(deleted, 0) = 0
""", NutMap.NEW().addv("id", id)));
}
@At("/save")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("congress.signIn.manage")
@SLog(tag = "职代会-会议签到", msg = "保存会议签到配置")
public Result signInSave(@Param("id") String id,
@Param("meetingName") String meetingName,
@Param("sessionId") String sessionId,
@Param("signInStartTime") String signInStartTime,
@Param("signInEndTime") String signInEndTime,
@Param("remark") String remark) {
if (StrUtil.hasBlank(meetingName, sessionId, signInStartTime, signInEndTime)) {
return Result.error("届次、会议名称和签到时间不能为空");
}
Date startTime = congressManageService.parseDate(signInStartTime);
Date endTime = congressManageService.parseDate(signInEndTime);
if (startTime == null || endTime == null) {
return Result.error("签到时间格式不正确");
}
if (!endTime.after(startTime)) {
return Result.error("签到结束时间必须晚于开始时间");
}
Date now = new Date();
Chain chain = Chain.make("meeting_name", meetingName.trim())
.add("session_id", sessionId)
.add("time_id", null)
.add("sign_in_start_time", startTime)
.add("sign_in_end_time", endTime)
.add("remark", StrUtil.blankToDefault(remark, null))
.add("update_time", now);
if (StrUtil.isBlank(id)) {
chain.add("id", R.UU32())
.add("sign_in_count", 0)
.add("create_time", now)
.add("deleted", 0);
dao.insert("congress_sign_in_meeting", chain);
} else {
dao.update("congress_sign_in_meeting", chain,
Cnd.where("id", "=", id).and("deleted", "=", 0));
}
return Result.success();
}
@At("/delete")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("congress.signIn.manage")
@SLog(tag = "职代会-会议签到", msg = "删除会议签到配置")
public Result signInDelete(@Param("id") String id) {
if (congressManageService.count("""
SELECT COUNT(1)
FROM congress_sign_in
WHERE meeting_id = @id
AND COALESCE(deleted, 0) = 0
""", NutMap.NEW().addv("id", id)) > 0) {
return Result.error("该会议已有签到记录,不能删除");
}
congressManageService.softDelete("congress_sign_in_meeting", id);
return Result.success();
}
}
@@ -0,0 +1,271 @@
package com.budwk.app.zhgh.democratic.congress.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.democratic.congress.service.CongressManageService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.util.Date;
@IocBean
@At("/platform/congress/materials/issue")
@Ok("json:full")
public class CongressVotingIssueController {
@Inject
private Dao dao;
@Inject
private CongressManageService congressManageService;
@At("/index")
@Ok("beetl:/platform/zhgh/democratic/congress/materials/issue/index.html")
@SaCheckPermission("congress.vote.issue")
public void index() {
}
@At("/sessions")
@SaCheckPermission("congress.vote.issue")
public Result sessions() {
return Result.success(congressManageService.sessionOptions());
}
@At("/types")
@SaCheckPermission("congress.vote.issue")
public Result types() {
return Result.success(congressManageService.typeOptions());
}
@At("/materials")
@SaCheckPermission("congress.vote.issue")
public Result materials(@Param("sessionId") String sessionId, @Param("typeId") String typeId) {
return Result.success(congressManageService.materialOptions(sessionId, typeId));
}
@At("/uploadMaterial")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("congress.vote.issue")
@SLog(tag = "职代会-投票管理", msg = "快捷上传投票资料")
public Result uploadMaterial(@Param("sessionId") String sessionId,
@Param("typeId") String typeId,
@Param("name") String name,
@Param("fileName") String fileName,
@Param("fileUrl") String fileUrl,
@Param("fileType") String fileType,
@Param("fileSize") String fileSize) {
if (StrUtil.hasBlank(sessionId, typeId, fileUrl)) {
return Result.error("届次、投票类型和文件不能为空");
}
if (congressManageService.count("""
SELECT COUNT(1)
FROM teacher_congress_session
WHERE id = @sessionId
AND delFlag = 0
""", NutMap.NEW().addv("sessionId", sessionId)) <= 0) {
return Result.error("所选届次不存在");
}
if (congressManageService.count("""
SELECT COUNT(1)
FROM congress_materials_type
WHERE id = @typeId
AND COALESCE(deleted, 0) = 0
AND COALESCE(is_vote, 0) = 1
""", NutMap.NEW().addv("typeId", typeId)) <= 0) {
return Result.error("所选投票类型不存在");
}
Date now = new Date();
String materialName = StrUtil.blankToDefault(name, StrUtil.blankToDefault(fileName, "资料")).trim();
String newId = R.UU32();
dao.insert("congress_materials", Chain.make("id", newId)
.add("name", materialName)
.add("session_id", sessionId)
.add("time_id", null)
.add("type_id", typeId)
.add("sort_no", 0)
.add("file_name", StrUtil.blankToDefault(fileName, materialName))
.add("file_url", fileUrl)
.add("file_type", congressManageService.normalizeFileType(fileType, fileName, fileUrl))
.add("file_size", StrUtil.blankToDefault(fileSize, null))
.add("create_time", now)
.add("update_time", now)
.add("deleted", 0));
return Result.success(NutMap.NEW()
.addv("id", newId)
.addv("name", materialName)
.addv("fileName", StrUtil.blankToDefault(fileName, materialName))
.addv("fileUrl", fileUrl));
}
@At("/pageData")
@SaCheckPermission("congress.vote.issue")
public Result issuePageData(PageForm pageForm,
@Param("sessionId") String sessionId,
@Param("name") String name) {
StringBuilder text = new StringBuilder("""
SELECT
i.id,
i.name,
i.description,
i.session_id AS sessionId,
i.time_id AS timeId,
i.type_id AS typeId,
i.materials_id AS materialsId,
i.voting_start_time AS votingStartTime,
i.voting_end_time AS votingEndTime,
mt.name AS typeName,
m.name AS materialsName,
m.file_name AS fileName,
m.file_url AS fileUrl,
tcs.fullName AS sessionName,
(
SELECT COUNT(1)
FROM congress_voting_record r
WHERE r.issue_id = i.id
AND COALESCE(r.deleted, 0) = 0
) AS voteCount
FROM congress_voting_issue i
LEFT JOIN congress_materials_type mt ON mt.id = i.type_id
LEFT JOIN congress_materials m ON m.id = i.materials_id
LEFT JOIN teacher_congress_session tcs ON tcs.id = i.session_id AND tcs.delFlag = 0
WHERE COALESCE(i.deleted, 0) = 0
""");
NutMap params = NutMap.NEW();
congressManageService.appendEquals(text, params, "i.session_id", "sessionId", sessionId);
if (StrUtil.isNotBlank(name)) {
text.append(" AND i.name LIKE @name");
params.addv("name", "%" + name.trim() + "%");
}
text.append(" ORDER BY i.create_time DESC, i.id DESC");
return congressManageService.page(pageForm, text.toString(), params);
}
@At("/get")
@SaCheckPermission("congress.vote.issue")
public Result issueGet(@Param("id") String id) {
return Result.success(congressManageService.fetchMap("""
SELECT
id,
name,
description,
content,
session_id AS sessionId,
time_id AS timeId,
type_id AS typeId,
materials_id AS materialsId,
voting_type AS votingType,
voting_start_time AS votingStartTime,
voting_end_time AS votingEndTime,
voting_content AS votingContent,
voting_content_item_header AS votingContentItemHeader,
voting_content_option_header AS votingContentOptionHeader
FROM congress_voting_issue
WHERE id = @id
AND COALESCE(deleted, 0) = 0
""", NutMap.NEW().addv("id", id)));
}
@At("/save")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("congress.vote.issue")
@SLog(tag = "职代会-投票管理", msg = "保存投票议题")
public Result issueSave(@Param("id") String id,
@Param("name") String name,
@Param("description") String description,
@Param("sessionId") String sessionId,
@Param("typeId") String typeId,
@Param("materialsId") String materialsId,
@Param("votingStartTime") String votingStartTime,
@Param("votingEndTime") String votingEndTime,
@Param("votingContent") String votingContent,
@Param("votingContentItemHeader") String votingContentItemHeader,
@Param("votingContentOptionHeader") String votingContentOptionHeader) {
if (StrUtil.hasBlank(name, sessionId, typeId, materialsId,
votingStartTime, votingEndTime, votingContent)) {
return Result.error("届次、会议名称、投票类型、资料、投票时间和投票内容不能为空");
}
Date startTime = congressManageService.parseDate(votingStartTime);
Date endTime = congressManageService.parseDate(votingEndTime);
if (startTime == null || endTime == null || !endTime.after(startTime)) {
return Result.error("投票结束时间必须晚于开始时间");
}
if (!JSONUtil.isTypeJSONArray(votingContent) || JSONUtil.parseArray(votingContent).isEmpty()) {
return Result.error("请至少配置一个投票事项");
}
NutMap session = congressManageService.fetchMap("""
SELECT id, fullName AS `sessionName`
FROM teacher_congress_session
WHERE id = @id
AND delFlag = 0
""", NutMap.NEW().addv("id", sessionId));
if (session == null) {
return Result.error("所选届次不存在");
}
Date now = new Date();
Chain chain = Chain.make("name", name.trim())
.add("description", StrUtil.blankToDefault(description, null))
.add("session_id", sessionId)
.add("session_name", session.getString("sessionName"))
.add("time_id", null)
.add("time_name", null)
.add("type_id", typeId)
.add("materials_id", materialsId)
.add("voting_type", 1)
.add("voting_start_time", startTime)
.add("voting_end_time", endTime)
.add("voting_content", votingContent)
.add("voting_content_item_header", StrUtil.blankToDefault(votingContentItemHeader, "表决事项"))
.add("voting_content_option_header", StrUtil.blankToDefault(votingContentOptionHeader, "表决内容"))
.add("update_time", now);
if (StrUtil.isBlank(id)) {
chain.add("id", R.UU32())
.add("create_time", now)
.add("deleted", 0);
dao.insert("congress_voting_issue", chain);
} else {
if (congressManageService.count("""
SELECT COUNT(1)
FROM congress_voting_record
WHERE issue_id = @id
AND COALESCE(deleted, 0) = 0
""", NutMap.NEW().addv("id", id)) > 0) {
return Result.error("该议题已有投票记录,不能修改");
}
dao.update("congress_voting_issue", chain,
Cnd.where("id", "=", id).and("deleted", "=", 0));
}
return Result.success();
}
@At("/delete")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("congress.vote.issue")
@SLog(tag = "职代会-投票管理", msg = "删除投票议题")
public Result issueDelete(@Param("id") String id) {
if (congressManageService.count("""
SELECT COUNT(1)
FROM congress_voting_record
WHERE issue_id = @id
AND COALESCE(deleted, 0) = 0
""", NutMap.NEW().addv("id", id)) > 0) {
return Result.error("该议题已有投票记录,不能删除");
}
congressManageService.softDelete("congress_voting_issue", id);
return Result.success();
}
}
@@ -0,0 +1,134 @@
package com.budwk.app.zhgh.democratic.congress.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.democratic.congress.service.CongressManageService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.util.Date;
@IocBean
@At("/platform/congress/materials/statistical")
@Ok("json:full")
public class CongressVotingStatisticalController {
@Inject
private Dao dao;
@Inject
private CongressManageService congressManageService;
@At("/index")
@Ok("beetl:/platform/zhgh/democratic/congress/materials/statistical/index.html")
@SaCheckPermission("congress.vote.statistical")
public void index() {
}
@At("/sessions")
@SaCheckPermission("congress.vote.statistical")
public Result sessions() {
return Result.success(congressManageService.sessionOptions());
}
@At("/pageData")
@SaCheckPermission("congress.vote.statistical")
public Result statisticalPageData(PageForm pageForm,
@Param("sessionId") String sessionId,
@Param("name") String name) {
StringBuilder text = new StringBuilder("""
SELECT
i.id,
i.name,
i.description,
i.session_id AS sessionId,
i.time_id AS timeId,
i.type_id AS typeId,
i.materials_id AS materialsId,
i.voting_start_time AS votingStartTime,
i.voting_end_time AS votingEndTime,
mt.name AS typeName,
m.name AS materialsName,
m.file_name AS fileName,
m.file_url AS fileUrl,
tcs.fullName AS sessionName,
(
SELECT COUNT(1)
FROM congress_voting_record r
WHERE r.issue_id = i.id
AND COALESCE(r.deleted, 0) = 0
) AS voteCount
FROM congress_voting_issue i
LEFT JOIN congress_materials_type mt ON mt.id = i.type_id
LEFT JOIN congress_materials m ON m.id = i.materials_id
LEFT JOIN teacher_congress_session tcs ON tcs.id = i.session_id AND tcs.delFlag = 0
WHERE COALESCE(i.deleted, 0) = 0
""");
NutMap params = NutMap.NEW();
congressManageService.appendEquals(text, params, "i.session_id", "sessionId", sessionId);
if (StrUtil.isNotBlank(name)) {
text.append(" AND i.name LIKE @name");
params.addv("name", "%" + name.trim() + "%");
}
text.append(" ORDER BY i.create_time DESC, i.id DESC");
return congressManageService.page(pageForm, text.toString(), params);
}
@At("/records")
@SaCheckPermission("congress.vote.statistical")
public Result voteRecords(@Param("issueId") String issueId) {
Sql sql = Sqls.create("""
SELECT
r.id,
r.emplid,
r.name,
r.vote_result AS voteResult,
r.create_time AS createTime,
COALESCE(rep.union_id, du.union_id) AS unionId,
du.union_name AS unionName,
COALESCE(rep.delegation_name, d.name) AS delegationName
FROM congress_voting_record r
LEFT JOIN congress_rep rep ON rep.id = r.rep_id
LEFT JOIN congress_delegation d ON d.id = r.delegation_id
LEFT JOIN (
SELECT delegation_id, time_id, union_id, union_name
FROM (
SELECT
delegation_id,
time_id,
union_id,
union_name,
ROW_NUMBER() OVER (
PARTITION BY delegation_id, time_id
ORDER BY create_time DESC, id DESC
) AS row_num
FROM congress_delegation_union
WHERE COALESCE(deleted, 0) = 0
) latest_union
WHERE row_num = 1
) du ON du.delegation_id = r.delegation_id AND du.time_id = r.time_id
WHERE r.issue_id = @issueId
AND COALESCE(r.deleted, 0) = 0
ORDER BY r.create_time DESC, r.id DESC
""");
sql.setParam("issueId", issueId);
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
return Result.success(sql.getList(NutMap.class));
}
}
@@ -0,0 +1,139 @@
package com.budwk.app.zhgh.democratic.congress.h5controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.democratic.congress.service.CongressH5Service;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
@IocBean
@At("/platform/h5/congress")
@Ok("json:full")
public class CongressH5Controller {
@Inject
private CongressH5Service congressH5Service;
@At("")
@Ok("beetl:/platform/zhghh5/democratic/congress/index.html")
@SaCheckLogin
public void index() {
}
@At("/materials/list")
@Ok("beetl:/platform/zhghh5/democratic/congress/materials/list.html")
@SaCheckLogin
public void materialsListPage() {
}
@At("/meeting/list")
@Ok("beetl:/platform/zhghh5/democratic/congress/meeting/list.html")
@SaCheckLogin
public void meetingListPage() {
}
@At("/meeting/remark")
@Ok("beetl:/platform/zhghh5/democratic/congress/meeting/remark.html")
@SaCheckLogin
public void meetingRemarkPage() {
}
@At("/pdf")
@Ok("beetl:/platform/zhghh5/democratic/congress/pdf.html")
@SaCheckLogin
public void pdfPage() {
}
@At("/vote")
@Ok("beetl:/platform/zhghh5/democratic/congress/vote.html")
@SaCheckLogin
public void votePage() {
}
@At("/voted")
@Ok("beetl:/platform/zhghh5/democratic/congress/voted.html")
@SaCheckLogin
public void votedPage() {
}
@At("/repTimes")
@SaCheckLogin
public Result repTimes(@Param("emplid") String emplid) {
return Result.success(congressH5Service.repTimes(emplid));
}
@At("/materialsTypeList")
@SaCheckLogin
public Result materialsTypeList() {
return Result.success(congressH5Service.materialsTypeList());
}
@At("/materialsList")
@SaCheckLogin
public Result materialsList(@Param("typeId") String typeId,
@Param("timeId") String timeId,
@Param("emplid") String emplid,
@Param("searchKeyword") String searchKeyword,
@Param("fileType") String fileType,
@Param("uploadTimeOrder") String uploadTimeOrder,
@Param("pageNumber") Integer pageNumber,
@Param("pageSize") Integer pageSize) {
return Result.success(congressH5Service.materialsList(typeId, timeId, emplid, searchKeyword,
fileType, uploadTimeOrder, pageNumber, pageSize));
}
@At("/user/meetingList")
@SaCheckLogin
public Result userMeetingList(@Param("timeId") String timeId,
@Param("emplid") String emplid,
@Param("searchKeyword") String searchKeyword,
@Param("signTimeStatus") String signTimeStatus,
@Param("signInCountStatus") String signInCountStatus,
@Param("signStatus") String signStatus,
@Param("pageNumber") Integer pageNumber,
@Param("pageSize") Integer pageSize) {
return Result.success(congressH5Service.userMeetingList(timeId, emplid, searchKeyword,
signTimeStatus, signInCountStatus, signStatus, pageNumber, pageSize));
}
@At("/user/meetingRemark")
@SaCheckLogin
public Result meetingRemark(@Param("meetingId") String meetingId,
@Param("timeId") String timeId,
@Param("emplid") String emplid) {
return Result.success(congressH5Service.meetingRemark(meetingId, timeId, emplid));
}
@At("/signin")
@SaCheckLogin
@Aop(TransAop.READ_COMMITTED)
public Result signin(@Param("repId") String repId,
@Param("sessionId") String sessionId,
@Param("timeId") String timeId,
@Param("meetingId") String meetingId) {
return congressH5Service.signin(repId, sessionId, timeId, meetingId);
}
@At("/votingIssue")
@SaCheckLogin
public Result votingIssue(@Param("materialsId") String materialsId, @Param("repId") String repId) {
return Result.success(congressH5Service.votingIssue(materialsId, repId));
}
@At("/api/vote")
@SaCheckLogin
@Aop(TransAop.READ_COMMITTED)
public Result voteSubmit(@Param("issueId") String issueId,
@Param("repId") String repId,
@Param("sessionId") String sessionId,
@Param("timeId") String timeId,
@Param("delegationId") String delegationId,
@Param("voteResult") String voteResult) {
return congressH5Service.voteSubmit(issueId, repId, sessionId, timeId, delegationId, voteResult);
}
}
@@ -0,0 +1,48 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Default;
import java.io.Serializable;
import java.util.Date;
/**
* 职代会历史表公共审计字段。
*
* <p>这些表使用下划线字段和 DATETIME 时间,不能继承使用驼峰字段、时间戳的通用 BaseModel。</p>
*/
@Data
public abstract class CongressBaseModel implements Serializable {
private static final long serialVersionUID = 1L;
@Column("create_by")
@Comment("创建人")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String createBy;
@Column("create_time")
@Comment("创建时间")
@ColDefine(type = ColType.DATETIME)
private Date createTime;
@Column("update_by")
@Comment("修改人")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String updateBy;
@Column("update_time")
@Comment("修改时间")
@ColDefine(type = ColType.DATETIME)
private Date updateTime;
@Column("deleted")
@Comment("删除标记")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean deleted;
}
@@ -0,0 +1,74 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_committee_elect_quota")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会委员预选分配")
@TableIndexes({
@Index(name = "idx_congress_committee_elect_quota_session", fields = {"sessionId"}, unique = false)
})
public class CongressCommitteeElectQuota extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("zbztjmes")
@Comment("筹备组推荐名额数")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer zbztjmes;
@Column("wyhyxrs")
@Comment("委员会预选人数")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer wyhyxrs;
@Column("dbttjzs")
@Comment("代表团推荐总数")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer dbttjzs;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("session_name")
@Comment("届次名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String sessionName;
@Column("first_preselected_begin_time")
@Comment("第一次预选开始时间")
@ColDefine(type = ColType.DATETIME)
private Date firstPreselectedBeginTime;
@Column("first_preselected_end_time")
@Comment("第一次预选结束时间")
@ColDefine(type = ColType.DATETIME)
private Date firstPreselectedEndTime;
@Column("second_preselected_begin_time")
@Comment("第二次预选开始时间")
@ColDefine(type = ColType.DATETIME)
private Date secondPreselectedBeginTime;
@Column("second_preselected_end_time")
@Comment("第二次预选结束时间")
@ColDefine(type = ColType.DATETIME)
private Date secondPreselectedEndTime;
}
@@ -0,0 +1,62 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_committee_elect_rep")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会委员推选")
@TableIndexes({
@Index(name = "idx_congress_committee_elect_rep_session", fields = {"sessionId"}, unique = false),
@Index(name = "idx_congress_committee_elect_rep_delegation", fields = {"delegationId"}, unique = false)
})
public class CongressCommitteeElectRep extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("rep_id")
@Comment("代表ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String repId;
@Column("type")
@Comment("推选类型")
@ColDefine(type = ColType.INT)
private Integer type;
@Column("times")
@Comment("第几次")
@ColDefine(type = ColType.INT)
private Integer times;
@Column("user_id")
@Comment("团长用户ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String userId;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("time_id")
@Comment("会议ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String timeId;
@Column("delegation_id")
@Comment("代表团ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String delegationId;
}
@@ -0,0 +1,80 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_delegation")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会代表团")
@TableIndexes({
@Index(name = "idx_congress_delegation_time", fields = {"timeId"}, unique = false)
})
public class CongressDelegation extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("name")
@Comment("代表团名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String name;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("session_name")
@Comment("届次名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String sessionName;
@Column("time_id")
@Comment("会议ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String timeId;
@Column("time_name")
@Comment("会议名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String timeName;
@Column("code")
@Comment("编码")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String code;
@Column("official_number")
@Comment("正式代表人数")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer officialNumber;
@Column("attendance_number")
@Comment("列席代表人数")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer attendanceNumber;
@Column("invite_number")
@Comment("特邀代表人数")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer inviteNumber;
@Column("committee_allot_number")
@Comment("委员分配人数")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer committeeAllotNumber;
}
@@ -0,0 +1,76 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_delegation_head")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会代表团团长")
@TableIndexes({
@Index(name = "idx_congress_delegation_head_delegation", fields = {"delegationId"}, unique = false)
})
public class CongressDelegationHead extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("time_id")
@Comment("会议ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String timeId;
@Column("delegation_id")
@Comment("代表团ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String delegationId;
@Column("user_id")
@Comment("用户ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String userId;
@Column("emplid")
@Comment("工号")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String emplid;
@Column("name")
@Comment("姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String name;
@Column("unit")
@Comment("单位")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String unit;
@Column("mobile")
@Comment("电话")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String mobile;
@Column("sex")
@Comment("性别")
@ColDefine(type = ColType.INT)
private Integer sex;
@Column("identity")
@Comment("身份")
@ColDefine(type = ColType.INT)
private Integer identity;
}
@@ -0,0 +1,66 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_delegation_rep")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会代表团代表")
@TableIndexes({
@Index(name = "idx_congress_delegation_rep_delegation", fields = {"delegationId"}, unique = false)
})
public class CongressDelegationRep extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("delegation_id")
@Comment("代表团ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String delegationId;
@Column("user_id")
@Comment("用户ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String userId;
@Column("empid")
@Comment("工号")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String empid;
@Column("name")
@Comment("姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String name;
@Column("unit")
@Comment("单位")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String unit;
@Column("mobile")
@Comment("电话")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String mobile;
@Column("sex")
@Comment("性别")
@ColDefine(type = ColType.INT)
private Integer sex;
@Column("identity")
@Comment("身份")
@ColDefine(type = ColType.INT)
private Integer identity;
}
@@ -0,0 +1,47 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_delegation_union")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会代表团工会")
@TableIndexes({
@Index(name = "idx_congress_delegation_union_delegation", fields = {"delegationId"}, unique = false),
@Index(name = "idx_congress_delegation_union_time", fields = {"timeId"}, unique = false)
})
public class CongressDelegationUnion extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("delegation_id")
@Comment("代表团ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String delegationId;
@Column("time_id")
@Comment("会议ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String timeId;
@Column("union_id")
@Comment("工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unionId;
@Column("union_name")
@Comment("工会名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String unionName;
}
@@ -0,0 +1,72 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_materials")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会资料")
@TableIndexes({
@Index(name = "idx_congress_materials_time_type", fields = {"timeId", "typeId"}, unique = false)
})
public class CongressMaterials extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("time_id")
@Comment("会议ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String timeId;
@Column("type_id")
@Comment("资料类型ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String typeId;
@Column("sort_no")
@Comment("排序")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer sortNo;
@Column("name")
@Comment("资料名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String name;
@Column("file_name")
@Comment("文件名")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String fileName;
@Column("file_url")
@Comment("文件地址")
@ColDefine(type = ColType.VARCHAR, width = 1000)
private String fileUrl;
@Column("file_type")
@Comment("文件类型")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String fileType;
@Column("file_size")
@Comment("文件大小")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String fileSize;
}
@@ -0,0 +1,40 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_materials_type")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会资料类型")
public class CongressMaterialsType extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("sort_no")
@Comment("排序")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer sortNo;
@Column("name")
@Comment("名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String name;
@Column("is_vote")
@Comment("是否表决类")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean isVote;
}
@@ -0,0 +1,62 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_org")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会机构")
@TableIndexes({
@Index(name = "idx_congress_org_session", fields = {"sessionId"}, unique = false),
@Index(name = "idx_congress_org_parent", fields = {"parentId"}, unique = false)
})
public class CongressOrg extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("name")
@Comment("机构名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String name;
@Column("code")
@Comment("机构代码")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String code;
@Column("type")
@Comment("机构类别")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String type;
@Column("remark")
@Comment("备注")
@ColDefine(type = ColType.VARCHAR, width = 1000)
private String remark;
@Column("parent_id")
@Comment("父节点")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String parentId;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("session_name")
@Comment("届次名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String sessionName;
}
@@ -0,0 +1,87 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_org_member")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会机构成员")
@TableIndexes({
@Index(name = "idx_congress_org_member_org", fields = {"orgId"}, unique = false),
@Index(name = "idx_congress_org_member_session", fields = {"sessionId"}, unique = false)
})
public class CongressOrgMember extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("user_id")
@Comment("用户ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String userId;
@Column("empid")
@Comment("工号")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String empid;
@Column("name")
@Comment("姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String name;
@Column("unit")
@Comment("单位")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String unit;
@Column("mobile")
@Comment("电话")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String mobile;
@Column("sex")
@Comment("性别")
@ColDefine(type = ColType.INT)
private Integer sex;
@Column("identity")
@Comment("身份")
@ColDefine(type = ColType.INT)
private Integer identity;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("session_name")
@Comment("届次名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String sessionName;
@Column("org_id")
@Comment("机构ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String orgId;
@Column("role_id")
@Comment("角色ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String roleId;
@Column("role_name")
@Comment("角色名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String roleName;
}
@@ -0,0 +1,171 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_rep")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会代表")
@TableIndexes({
@Index(name = "idx_congress_rep_emplid", fields = {"emplid"}, unique = false),
@Index(name = "idx_congress_rep_time_emplid", fields = {"timeId", "emplid"}, unique = false),
@Index(name = "idx_congress_rep_session_id", fields = {"sessionId"}, unique = false)
})
public class CongressRep extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("time_id")
@Comment("会议ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String timeId;
@Column("session_name")
@Comment("届次名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String sessionName;
@Column("time_name")
@Comment("会议名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String timeName;
@Column("user_id")
@Comment("用户ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String userId;
@Column("name")
@Comment("姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String name;
@Column("emplid")
@Comment("工号")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String emplid;
@Column("sex")
@Comment("性别")
@ColDefine(type = ColType.VARCHAR, width = 16)
private String sex;
@Column("birthdate")
@Comment("出生日期")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String birthdate;
@Column("politics_status")
@Comment("政治面貌")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String politicsStatus;
@Column("position")
@Comment("职务")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String position;
@Column("education")
@Comment("学历")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String education;
@Column("work_unit")
@Comment("工作单位")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String workUnit;
@Column("work_experience")
@Comment("工作经历")
@ColDefine(type = ColType.TEXT)
private String workExperience;
@Column("tags")
@Comment("标签")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String tags;
@Column("filling_status")
@Comment("填报状态")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String fillingStatus;
@Column("identity")
@Comment("身份")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String identity;
@Column("type")
@Comment("代表类型")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String type;
@Column("is_gdh")
@Comment("是否工代会代表")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean isGdh;
@Column("is_zdh")
@Comment("是否职代会代表")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean isZdh;
@Column("union_id")
@Comment("工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unionId;
@Column("delegation_id")
@Comment("代表团ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String delegationId;
@Column("delegation_name")
@Comment("代表团名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String delegationName;
@Column("supplement")
@Comment("补充说明")
@ColDefine(type = ColType.TEXT)
private String supplement;
@Column("status")
@Comment("状态")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer status;
@Column("precinct_union_id")
@Comment("选区工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String precinctUnionId;
@Column("precinct_union_name")
@Comment("选区工会名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String precinctUnionName;
@Column("nation")
@Comment("民族")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String nation;
}
@@ -0,0 +1,111 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_rep_supplement")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会代表增补")
@TableIndexes({
@Index(name = "idx_congress_rep_supplement_time", fields = {"timeId"}, unique = false)
})
public class CongressRepSupplement extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("precinct_union_id")
@Comment("选区")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String precinctUnionId;
@Column("time_id")
@Comment("会议ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String timeId;
@Column("time_name")
@Comment("会议名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String timeName;
@Column("add_user_id")
@Comment("增加代表用户ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String addUserId;
@Column("add_user_union_id")
@Comment("增加代表工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String addUserUnionId;
@Column("add_user_name")
@Comment("增加代表姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String addUserName;
@Column("add_emplid")
@Comment("增加代表工号")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String addEmplid;
@Column("sub_user_id")
@Comment("替换代表用户ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String subUserId;
@Column("sub_user_name")
@Comment("替换代表姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String subUserName;
@Column("sub_emplid")
@Comment("替换代表工号")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String subEmplid;
@Column("reason")
@Comment("原因")
@ColDefine(type = ColType.VARCHAR, width = 1000)
private String reason;
@Column("add_precinct_union_id")
@Comment("增加选区工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String addPrecinctUnionId;
@Column("add_precinct_union_name")
@Comment("增加选区工会名")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String addPrecinctUnionName;
@Column("sub_precinct_union_id")
@Comment("替换选区工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String subPrecinctUnionId;
@Column("sub_precinct_union_name")
@Comment("替换选区工会名")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String subPrecinctUnionName;
@Column("type")
@Comment("类型")
@ColDefine(type = ColType.INT)
private Integer type;
}
@@ -0,0 +1,66 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_sessions")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会届次")
@TableIndexes({
@Index(name = "idx_congress_sessions_union_id", fields = {"unionId"}, unique = false)
})
public class CongressSessions extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("name")
@Comment("届次名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String name;
@Column("year")
@Comment("年份")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String year;
@Column("description")
@Comment("描述")
@ColDefine(type = ColType.VARCHAR, width = 1000)
private String description;
@Column("sc_session")
@Comment("省产届次")
@ColDefine(type = ColType.INT)
private Integer scSession;
@Column("uc_session")
@Comment("校产届次")
@ColDefine(type = ColType.INT)
private Integer ucSession;
@Column("union_id")
@Comment("工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unionId;
@Column("type")
@Comment("会类型")
@ColDefine(type = ColType.INT)
private Integer type;
@Column("union_name")
@Comment("工会名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String unionName;
}
@@ -0,0 +1,62 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_sign_in")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会签到记录")
@TableIndexes({
@Index(name = "uk_congress_sign_in_rep_meeting", fields = {"repId", "meetingId", "deleted"}, unique = true),
@Index(name = "idx_congress_sign_in_meeting", fields = {"meetingId"}, unique = false)
})
public class CongressSignIn extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("time_id")
@Comment("会议ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String timeId;
@Column("meeting_id")
@Comment("签到会议ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String meetingId;
@Column("delegation_id")
@Comment("代表团ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String delegationId;
@Column("rep_id")
@Comment("代表ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String repId;
@Column("emplid")
@Comment("工号")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String emplid;
@Column("name")
@Comment("姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String name;
}
@@ -0,0 +1,62 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_sign_in_meeting")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会签到会议")
@TableIndexes({
@Index(name = "idx_congress_sign_in_meeting_time", fields = {"timeId"}, unique = false)
})
public class CongressSignInMeeting extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("meeting_name")
@Comment("会议名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String meetingName;
@Column("sign_in_start_time")
@Comment("签到开始时间")
@ColDefine(type = ColType.DATETIME)
private Date signInStartTime;
@Column("sign_in_end_time")
@Comment("签到结束时间")
@ColDefine(type = ColType.DATETIME)
private Date signInEndTime;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("time_id")
@Comment("会议ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String timeId;
@Column("remark")
@Comment("备注")
@ColDefine(type = ColType.VARCHAR, width = 1000)
private String remark;
@Column("sign_in_count")
@Comment("签到人数")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer signInCount;
}
@@ -0,0 +1,56 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_times")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会会议")
@TableIndexes({
@Index(name = "idx_congress_times_session_id", fields = {"sessionId"}, unique = false)
})
public class CongressTimes extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("year")
@Comment("年份")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String year;
@Column("name")
@Comment("会议名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String name;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("session_name")
@Comment("届次名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String sessionName;
@Column("sc_times")
@Comment("省产次数")
@ColDefine(type = ColType.INT)
private Integer scTimes;
@Column("uc_times")
@Comment("校产次数")
@ColDefine(type = ColType.INT)
private Integer ucTimes;
}
@@ -0,0 +1,69 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_union")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会工会名额")
@TableIndexes({
@Index(name = "idx_congress_union_session", fields = {"sessionId"}, unique = false),
@Index(name = "idx_congress_union_delegation", fields = {"delegationId"}, unique = false)
})
public class CongressUnion extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("session_name")
@Comment("届次名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String sessionName;
@Column("union_id")
@Comment("工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unionId;
@Column("union_name")
@Comment("工会名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String unionName;
@Column("quota_config")
@Comment("名额配置")
@ColDefine(type = ColType.TEXT)
private String quotaConfig;
@Column("member_number")
@Comment("会员数")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer memberNumber;
@Column("delegation_id")
@Comment("代表团ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String delegationId;
@Column("sort_no")
@Comment("排序")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer sortNo;
}
@@ -0,0 +1,107 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_voting_issue")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会议题表决")
@TableIndexes({
@Index(name = "idx_congress_voting_issue_materials", fields = {"materialsId"}, unique = false),
@Index(name = "idx_congress_voting_issue_time", fields = {"timeId"}, unique = false)
})
public class CongressVotingIssue extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("session_name")
@Comment("届次名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String sessionName;
@Column("time_id")
@Comment("会议ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String timeId;
@Column("time_name")
@Comment("会议名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String timeName;
@Column("name")
@Comment("议题名称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String name;
@Column("description")
@Comment("描述")
@ColDefine(type = ColType.VARCHAR, width = 1000)
private String description;
@Column("content")
@Comment("内容")
@ColDefine(type = ColType.TEXT)
private String content;
@Column("voting_type")
@Comment("表决类型")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String votingType;
@Column("voting_start_time")
@Comment("表决开始时间")
@ColDefine(type = ColType.DATETIME)
private Date votingStartTime;
@Column("voting_end_time")
@Comment("表决结束时间")
@ColDefine(type = ColType.DATETIME)
private Date votingEndTime;
@Column("voting_content")
@Comment("表决内容")
@ColDefine(type = ColType.TEXT)
private String votingContent;
@Column("voting_content_item_header")
@Comment("表决项表头")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String votingContentItemHeader;
@Column("voting_content_option_header")
@Comment("表决选项表头")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String votingContentOptionHeader;
@Column("materials_id")
@Comment("资料ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String materialsId;
@Column("type_id")
@Comment("资料类型ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String typeId;
@Column("delegation_id")
@Comment("代表团ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String delegationId;
}
@@ -0,0 +1,72 @@
package com.budwk.app.zhgh.democratic.congress.models;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@Table("congress_voting_record")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("职代会表决记录")
@TableIndexes({
@Index(name = "uk_congress_voting_record_rep_issue", fields = {"repId", "issueId", "deleted"}, unique = true),
@Index(name = "idx_congress_voting_record_issue", fields = {"issueId"}, unique = false)
})
public class CongressVotingRecord extends CongressBaseModel {
@Name
@Comment("主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column("issue_id")
@Comment("议题ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String issueId;
@Column("rep_id")
@Comment("代表ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String repId;
@Column("emplid")
@Comment("工号")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String emplid;
@Column("name")
@Comment("姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String name;
@Column("session_id")
@Comment("届次ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sessionId;
@Column("time_id")
@Comment("会议ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String timeId;
@Column("delegation_id")
@Comment("代表团ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String delegationId;
@Column("vote_result")
@Comment("表决结果")
@ColDefine(type = ColType.TEXT)
private String voteResult;
@Column("user_id")
@Comment("用户ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String userId;
}
@@ -0,0 +1,30 @@
package com.budwk.app.zhgh.democratic.congress.service;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.page.Pagination;
import org.nutz.lang.util.NutMap;
import java.util.List;
public interface CongressH5Service {
List<NutMap> repTimes(String emplid);
List<NutMap> materialsTypeList();
Pagination<NutMap> materialsList(String typeId, String sessionId, String emplid, String searchKeyword,
String fileType, String uploadTimeOrder, Integer pageNumber, Integer pageSize);
Pagination<NutMap> userMeetingList(String sessionId, String emplid, String searchKeyword,
String signTimeStatus, String signInCountStatus, String signStatus,
Integer pageNumber, Integer pageSize);
NutMap meetingRemark(String meetingId, String sessionId, String emplid);
Result signin(String repId, String sessionId, String timeId, String meetingId);
NutMap votingIssue(String materialsId, String repId);
Result voteSubmit(String issueId, String repId, String sessionId, String timeId,
String delegationId, String voteResult);
}
@@ -0,0 +1,179 @@
package com.budwk.app.zhgh.democratic.congress.service;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* 职代会管理公共服务类。
*
* <p>本服务不注册页面路由,只集中维护六个菜单控制器共用的查询和基础操作。</p>
*/
@IocBean
public class CongressManageService {
@Inject
private Dao dao;
@Inject
private BaseService<?> baseService;
public List<NutMap> sessionOptions() {
return listMaps("""
SELECT id AS `id`, fullName AS `fullName`
FROM teacher_congress_session
WHERE delFlag = 0
ORDER BY startDate desc, createdAt desc
""");
}
public List<NutMap> typeOptions() {
return listMaps("""
SELECT id, name, sort_no AS sortNo, is_vote AS isVote
FROM congress_materials_type
WHERE COALESCE(deleted, 0) = 0
ORDER BY sort_no, create_time, id
""");
}
public List<NutMap> meetingOptions(String sessionId) {
Sql sql = Sqls.create("""
SELECT id, meeting_name AS meetingName
FROM congress_sign_in_meeting
WHERE COALESCE(deleted, 0) = 0
AND session_id = @sessionId
ORDER BY sign_in_start_time DESC, create_time DESC
""");
sql.setParam("sessionId", sessionId);
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
return sql.getList(NutMap.class);
}
public List<NutMap> materialOptions(String sessionId, String typeId) {
StringBuilder sqlText = new StringBuilder("""
SELECT
m.id,
m.name,
m.type_id AS typeId,
m.file_name AS fileName,
m.file_url AS fileUrl
FROM congress_materials m
WHERE COALESCE(m.deleted, 0) = 0
AND m.session_id = @sessionId
""");
if (StrUtil.isNotBlank(typeId)) {
sqlText.append(" AND m.type_id = @typeId");
}
sqlText.append(" ORDER BY m.sort_no, m.create_time DESC");
Sql sql = Sqls.create(sqlText.toString());
sql.setParam("sessionId", sessionId);
sql.setParam("typeId", typeId);
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
return sql.getList(NutMap.class);
}
public Result page(PageForm pageForm, String sqlText, NutMap params) {
Sql sql = Sqls.create(sqlText);
params.forEach(sql::setParam);
Pagination pagination = baseService.listPageMap(
pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
}
public List<NutMap> listMaps(String sqlText) {
Sql sql = Sqls.create(sqlText);
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
return sql.getList(NutMap.class);
}
public NutMap fetchMap(String sqlText, NutMap params) {
Sql sql = Sqls.create(sqlText);
params.forEach(sql::setParam);
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
return sql.getObject(NutMap.class);
}
public int count(String sqlText, NutMap params) {
Sql sql = Sqls.create(sqlText);
params.forEach(sql::setParam);
sql.setCallback(Sqls.callback.integer());
dao.execute(sql);
return sql.getInt();
}
public void softDelete(String tableName, String id) {
dao.update(tableName,
Chain.make("deleted", 1).add("update_time", new Date()),
Cnd.where("id", "=", id).and("deleted", "=", 0));
}
public Date parseDate(String value) {
if (StrUtil.isBlank(value)) {
return null;
}
try {
return DateUtil.parse(value);
} catch (Exception e) {
return null;
}
}
public void appendEquals(StringBuilder sql, NutMap params,
String column, String paramName, Object value) {
if (value instanceof String && StrUtil.isBlank((String) value)) {
return;
}
if (value == null) {
return;
}
sql.append(" AND ").append(column).append(" = @").append(paramName);
params.addv(paramName, value);
}
public String normalizeFileType(String fileType, String fileName, String fileUrl) {
if (StrUtil.isNotBlank(fileType) && !fileType.contains("/")) {
return fileType.replace(".", "").trim().toLowerCase(Locale.ROOT);
}
String suffix = extractFileExtension(fileName);
if (StrUtil.isBlank(suffix)) {
suffix = extractFileExtension(fileUrl);
}
return StrUtil.blankToDefault(suffix, null);
}
private String extractFileExtension(String name) {
if (StrUtil.isBlank(name)) {
return "";
}
String value = name.split("\\?", 2)[0];
int slashIndex = Math.max(value.lastIndexOf('/'), value.lastIndexOf('\\'));
if (slashIndex >= 0) {
value = value.substring(slashIndex + 1);
}
int dotIndex = value.lastIndexOf('.');
if (dotIndex < 0 || dotIndex == value.length() - 1) {
return "";
}
return value.substring(dotIndex + 1).toLowerCase(Locale.ROOT);
}
}
@@ -0,0 +1,457 @@
package com.budwk.app.zhgh.democratic.congress.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.congress.service.CongressH5Service;
import org.nutz.dao.Chain;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import java.util.Date;
import java.util.List;
@IocBean
public class CongressH5ServiceImpl implements CongressH5Service {
@Inject
private Dao dao;
@Override
public List<NutMap> repTimes(String emplid) {
String loginName = loginName(emplid);
Sql sql = Sqls.create("""
SELECT
s.id,
s.id AS timeId,
COALESCE(s.fullName, CONCAT(s.year, '年职代会')) AS name,
s.year,
s.id AS sessionId,
s.fullName AS sessionName,
NULL AS type,
d.id AS repId,
d.delegationId AS delegationId,
dg.name AS delegationName
FROM teacher_congress_delegate d
INNER JOIN teacher_congress_session s ON s.id = d.sessionId
AND COALESCE(s.delFlag, 0) = 0
LEFT JOIN teacher_congress_delegation dg ON dg.id = d.delegationId
AND COALESCE(dg.delFlag, 0) = 0
WHERE d.loginName = @emplid
AND COALESCE(d.delFlag, 0) = 0
AND COALESCE(s.enable, 0) = 1
ORDER BY s.startDate DESC, s.createdAt DESC, s.id DESC
""");
sql.setParam("emplid", loginName);
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
return sql.getList(NutMap.class);
}
@Override
public List<NutMap> materialsTypeList() {
Sql sql = Sqls.create("""
SELECT
SUBSTRING_INDEX(
GROUP_CONCAT(
id
ORDER BY
CASE
WHEN id IN (
'congress_type_file_notice',
'congress_type_evaluation_vote',
'congress_type_issue_vote',
'congress_type_vote_notice',
'congress_type_prepare_material'
) THEN 0
ELSE 1
END,
sort_no ASC,
create_time ASC
),
',',
1
) AS id,
MIN(sort_no) AS sortNo,
name,
MAX(is_vote) AS isVote
FROM congress_materials_type
WHERE COALESCE(deleted, 0) = 0
GROUP BY name
ORDER BY sortNo ASC, name ASC
""");
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
return sql.getList(NutMap.class);
}
@Override
public Pagination<NutMap> materialsList(String typeId, String sessionId, String emplid, String searchKeyword,
String fileType, String uploadTimeOrder, Integer pageNumber, Integer pageSize) {
String loginName = loginName(emplid);
int currentPage = pageNumber == null || pageNumber < 1 ? 1 : pageNumber;
int currentSize = pageSize == null || pageSize < 1 ? 10 : Math.min(pageSize, 50);
String orderBy = "asc".equalsIgnoreCase(uploadTimeOrder) ? "ASC" : "DESC";
Sql sql = Sqls.create(("""
SELECT
cm.id,
cm.session_id AS sessionId,
COALESCE(cm.time_id, cm.session_id) AS timeId,
cm.type_id AS typeId,
cm.sort_no AS sortNo,
cm.name,
cm.file_name AS fileName,
cm.file_url AS fileUrl,
cm.file_type AS fileType,
cm.file_size AS fileSize,
cm.create_time AS uploadTime,
CASE
WHEN cvi.id IS NULL THEN NULL
WHEN cvr.id IS NOT NULL THEN true
ELSE false
END AS hasVoted,
CASE WHEN cvr.id IS NOT NULL THEN true ELSE false END AS voted
FROM congress_materials cm
INNER JOIN teacher_congress_delegate d ON d.sessionId = cm.session_id
AND d.loginName = @emplid
AND COALESCE(d.delFlag, 0) = 0
INNER JOIN congress_materials_type cmt ON cm.type_id = cmt.id
AND COALESCE(cmt.deleted, 0) = 0
LEFT JOIN congress_voting_issue cvi ON cm.id = cvi.materials_id
AND COALESCE(cvi.deleted, 0) = 0
AND COALESCE(cmt.is_vote, 0) = 1
LEFT JOIN congress_voting_record cvr ON cvi.id = cvr.issue_id
AND (cvr.rep_id = d.id OR cvr.emplid = @emplid)
AND COALESCE(cvr.deleted, 0) = 0
WHERE COALESCE(cm.deleted, 0) = 0
AND cm.type_id = @typeId
AND (cm.session_id = @sessionId OR cm.time_id = @sessionId)
AND (@searchKeyword = '' OR cm.name LIKE CONCAT('%%', @searchKeyword, '%%')
OR cm.file_name LIKE CONCAT('%%', @searchKeyword, '%%'))
AND (@fileType = '' OR FIND_IN_SET(LOWER(REPLACE(cm.file_type, '.', '')), @fileType) > 0
OR FIND_IN_SET(LOWER(SUBSTRING_INDEX(cm.file_name, '.', -1)), @fileType) > 0)
ORDER BY cm.create_time %s, cm.id DESC
""").formatted(orderBy));
sql.setParam("typeId", typeId);
sql.setParam("sessionId", sessionId);
sql.setParam("emplid", loginName);
sql.setParam("searchKeyword", StrUtil.blankToDefault(searchKeyword, ""));
sql.setParam("fileType", StrUtil.blankToDefault(fileType, ""));
sql.setPager(dao.createPager(currentPage, currentSize));
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
int totalCount = count("""
SELECT COUNT(DISTINCT cm.id)
FROM congress_materials cm
INNER JOIN teacher_congress_delegate d ON d.sessionId = cm.session_id
AND d.loginName = @emplid
AND COALESCE(d.delFlag, 0) = 0
WHERE COALESCE(cm.deleted, 0) = 0
AND cm.type_id = @typeId
AND (cm.session_id = @sessionId OR cm.time_id = @sessionId)
AND (@searchKeyword = '' OR cm.name LIKE CONCAT('%%', @searchKeyword, '%%')
OR cm.file_name LIKE CONCAT('%%', @searchKeyword, '%%'))
AND (@fileType = '' OR FIND_IN_SET(LOWER(REPLACE(cm.file_type, '.', '')), @fileType) > 0
OR FIND_IN_SET(LOWER(SUBSTRING_INDEX(cm.file_name, '.', -1)), @fileType) > 0)
""", NutMap.NEW()
.addv("typeId", typeId)
.addv("sessionId", sessionId)
.addv("emplid", loginName)
.addv("searchKeyword", StrUtil.blankToDefault(searchKeyword, ""))
.addv("fileType", StrUtil.blankToDefault(fileType, "")));
return new Pagination<>(currentPage, currentSize, totalCount, sql.getList(NutMap.class));
}
@Override
public Pagination<NutMap> userMeetingList(String sessionId, String emplid, String searchKeyword,
String signTimeStatus, String signInCountStatus, String signStatus,
Integer pageNumber, Integer pageSize) {
String loginName = loginName(emplid);
int currentPage = pageNumber == null || pageNumber < 1 ? 1 : pageNumber;
int currentSize = pageSize == null || pageSize < 1 ? 10 : Math.min(pageSize, 50);
Sql sql = Sqls.create("""
SELECT
m.id,
m.meeting_name AS meetingName,
m.sign_in_start_time AS signInStartTime,
m.sign_in_end_time AS signInEndTime,
m.session_id AS sessionId,
COALESCE(m.time_id, m.session_id) AS timeId,
m.remark,
(
SELECT COUNT(1)
FROM congress_sign_in s
WHERE s.meeting_id = m.id
AND COALESCE(s.deleted, 0) = 0
) AS signInCount,
si.id AS signInId,
si.create_time AS userSignInTime,
d.id AS repId
FROM congress_sign_in_meeting m
INNER JOIN teacher_congress_delegate d ON d.sessionId = m.session_id
AND d.loginName = @emplid
AND COALESCE(d.delFlag, 0) = 0
LEFT JOIN congress_sign_in si ON m.id = si.meeting_id
AND COALESCE(si.deleted, 0) = 0
AND si.rep_id = d.id
WHERE COALESCE(m.deleted, 0) = 0
AND (m.session_id = @sessionId OR m.time_id = @sessionId)
AND (@searchKeyword = '' OR m.meeting_name LIKE CONCAT('%', @searchKeyword, '%'))
AND (@signTimeStatus = ''
OR (@signTimeStatus = 'upcoming' AND m.sign_in_start_time > NOW())
OR (@signTimeStatus = 'ongoing' AND m.sign_in_start_time <= NOW() AND m.sign_in_end_time >= NOW())
OR (@signTimeStatus = 'ended' AND m.sign_in_end_time < NOW()))
AND (@signInCountStatus = ''
OR (@signInCountStatus = 'has' AND EXISTS (
SELECT 1 FROM congress_sign_in count_si
WHERE count_si.meeting_id = m.id AND COALESCE(count_si.deleted, 0) = 0))
OR (@signInCountStatus = 'none' AND NOT EXISTS (
SELECT 1 FROM congress_sign_in count_si
WHERE count_si.meeting_id = m.id AND COALESCE(count_si.deleted, 0) = 0)))
AND (@signStatus = ''
OR (@signStatus = 'signed' AND si.id IS NOT NULL)
OR (@signStatus = 'unsigned' AND si.id IS NULL))
ORDER BY m.sign_in_start_time DESC, m.create_time DESC
""");
sql.setParam("sessionId", sessionId);
sql.setParam("emplid", loginName);
sql.setParam("searchKeyword", StrUtil.blankToDefault(searchKeyword, ""));
sql.setParam("signTimeStatus", StrUtil.blankToDefault(signTimeStatus, ""));
sql.setParam("signInCountStatus", StrUtil.blankToDefault(signInCountStatus, ""));
sql.setParam("signStatus", StrUtil.blankToDefault(signStatus, ""));
sql.setPager(dao.createPager(currentPage, currentSize));
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
int totalCount = count("""
SELECT COUNT(DISTINCT m.id)
FROM congress_sign_in_meeting m
INNER JOIN teacher_congress_delegate d ON d.sessionId = m.session_id
AND d.loginName = @emplid
AND COALESCE(d.delFlag, 0) = 0
LEFT JOIN congress_sign_in si ON m.id = si.meeting_id
AND COALESCE(si.deleted, 0) = 0
AND si.rep_id = d.id
WHERE COALESCE(m.deleted, 0) = 0
AND (m.session_id = @sessionId OR m.time_id = @sessionId)
AND (@searchKeyword = '' OR m.meeting_name LIKE CONCAT('%', @searchKeyword, '%'))
AND (@signTimeStatus = ''
OR (@signTimeStatus = 'upcoming' AND m.sign_in_start_time > NOW())
OR (@signTimeStatus = 'ongoing' AND m.sign_in_start_time <= NOW() AND m.sign_in_end_time >= NOW())
OR (@signTimeStatus = 'ended' AND m.sign_in_end_time < NOW()))
AND (@signInCountStatus = ''
OR (@signInCountStatus = 'has' AND EXISTS (
SELECT 1 FROM congress_sign_in count_si
WHERE count_si.meeting_id = m.id AND COALESCE(count_si.deleted, 0) = 0))
OR (@signInCountStatus = 'none' AND NOT EXISTS (
SELECT 1 FROM congress_sign_in count_si
WHERE count_si.meeting_id = m.id AND COALESCE(count_si.deleted, 0) = 0)))
AND (@signStatus = ''
OR (@signStatus = 'signed' AND si.id IS NOT NULL)
OR (@signStatus = 'unsigned' AND si.id IS NULL))
""", NutMap.NEW()
.addv("sessionId", sessionId)
.addv("emplid", loginName)
.addv("searchKeyword", StrUtil.blankToDefault(searchKeyword, ""))
.addv("signTimeStatus", StrUtil.blankToDefault(signTimeStatus, ""))
.addv("signInCountStatus", StrUtil.blankToDefault(signInCountStatus, ""))
.addv("signStatus", StrUtil.blankToDefault(signStatus, "")));
return new Pagination<>(currentPage, currentSize, totalCount, sql.getList(NutMap.class));
}
@Override
public NutMap meetingRemark(String meetingId, String sessionId, String emplid) {
Sql sql = Sqls.create("""
SELECT
m.id,
m.meeting_name AS meetingName,
m.remark,
m.session_id AS sessionId,
COALESCE(m.time_id, m.session_id) AS timeId
FROM congress_sign_in_meeting m
INNER JOIN teacher_congress_delegate d ON d.sessionId = m.session_id
AND d.loginName = @emplid
AND COALESCE(d.delFlag, 0) = 0
WHERE m.id = @meetingId
AND COALESCE(m.deleted, 0) = 0
AND (m.session_id = @sessionId OR m.time_id = @sessionId)
LIMIT 1
""");
sql.setParam("meetingId", meetingId);
sql.setParam("sessionId", sessionId);
sql.setParam("emplid", loginName(emplid));
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
return sql.getObject(NutMap.class);
}
@Override
public Result signin(String repId, String sessionId, String timeId, String meetingId) {
if (StrUtil.hasBlank(repId, sessionId, meetingId)) {
return Result.error("签到参数不完整");
}
int count = count("""
SELECT COUNT(1)
FROM congress_sign_in
WHERE rep_id = @repId
AND meeting_id = @meetingId
AND COALESCE(deleted, 0) = 0
""", NutMap.NEW().addv("repId", repId).addv("meetingId", meetingId));
if (count > 0) {
return Result.success(true);
}
NutMap rep = fetchMap("""
SELECT id, userId, loginName, userName, delegationId
FROM teacher_congress_delegate
WHERE id = @repId
AND sessionId = @sessionId
AND COALESCE(delFlag, 0) = 0
LIMIT 1
""", NutMap.NEW().addv("repId", repId).addv("sessionId", sessionId));
if (rep == null) {
return Result.error("当前用户不是该届次职工代表,无法签到");
}
dao.insert("congress_sign_in", Chain.make("id", R.UU32())
.add("session_id", sessionId)
.add("time_id", StrUtil.blankToDefault(timeId, sessionId))
.add("meeting_id", meetingId)
.add("delegation_id", rep.getString("delegationId"))
.add("rep_id", repId)
.add("emplid", rep.getString("loginName"))
.add("name", rep.getString("userName"))
.add("create_time", new Date())
.add("update_time", new Date())
.add("deleted", false));
return Result.success(true);
}
@Override
public NutMap votingIssue(String materialsId, String repId) {
Sql sql = Sqls.create("""
SELECT
i.id,
i.session_id AS sessionId,
i.session_name AS sessionName,
COALESCE(i.time_id, i.session_id) AS timeId,
i.time_name AS timeName,
i.name,
i.description,
i.content,
i.voting_type AS votingType,
i.voting_start_time AS votingStartTime,
i.voting_end_time AS votingEndTime,
i.voting_content AS votingContent,
i.voting_content_item_header AS votingContentItemHeader,
i.voting_content_option_header AS votingContentOptionHeader,
i.materials_id AS materialsId,
i.type_id AS typeId,
i.delegation_id AS delegationId,
mt.name AS typeName,
CASE WHEN vr.id IS NULL THEN false ELSE true END AS voted,
vr.vote_result AS voteResult
FROM congress_voting_issue i
INNER JOIN teacher_congress_delegate d ON d.id = @repId
AND d.sessionId = i.session_id
AND COALESCE(d.delFlag, 0) = 0
LEFT JOIN congress_materials_type mt ON mt.id = i.type_id
AND COALESCE(mt.deleted, 0) = 0
LEFT JOIN congress_voting_record vr ON vr.issue_id = i.id
AND (vr.rep_id = d.id OR vr.emplid = d.loginName)
AND COALESCE(vr.deleted, 0) = 0
WHERE i.materials_id = @materialsId
AND COALESCE(i.deleted, 0) = 0
LIMIT 1
""");
sql.setParam("materialsId", materialsId);
sql.setParam("repId", repId);
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
return sql.getObject(NutMap.class);
}
@Override
public Result voteSubmit(String issueId, String repId, String sessionId, String timeId,
String delegationId, String voteResult) {
if (StrUtil.hasBlank(issueId, repId, sessionId, voteResult)) {
return Result.error("投票参数不完整");
}
NutMap issue = fetchMap("""
SELECT id, session_id AS sessionId, COALESCE(time_id, session_id) AS timeId
FROM congress_voting_issue
WHERE id = @issueId
AND session_id = @sessionId
AND COALESCE(deleted, 0) = 0
LIMIT 1
""", NutMap.NEW().addv("issueId", issueId).addv("sessionId", sessionId));
if (issue == null) {
return Result.error("投票议题不存在");
}
NutMap rep = fetchMap("""
SELECT id, userId, loginName, userName, delegationId
FROM teacher_congress_delegate
WHERE id = @repId
AND sessionId = @sessionId
AND COALESCE(delFlag, 0) = 0
LIMIT 1
""", NutMap.NEW().addv("repId", repId).addv("sessionId", sessionId));
if (rep == null) {
return Result.error("当前用户不是该届次职工代表,无法投票");
}
int count = count("""
SELECT COUNT(1)
FROM congress_voting_record
WHERE issue_id = @issueId
AND rep_id = @repId
AND COALESCE(deleted, 0) = 0
""", NutMap.NEW().addv("issueId", issueId).addv("repId", repId));
if (count > 0) {
return Result.error("您已投票,请勿重复提交");
}
dao.insert("congress_voting_record", Chain.make("id", R.UU32())
.add("issue_id", issueId)
.add("rep_id", repId)
.add("emplid", rep.getString("loginName"))
.add("name", rep.getString("userName"))
.add("session_id", sessionId)
.add("time_id", StrUtil.blankToDefault(timeId, issue.getString("timeId")))
.add("delegation_id", StrUtil.blankToDefault(delegationId, rep.getString("delegationId")))
.add("vote_result", voteResult)
.add("user_id", rep.getString("userId"))
.add("create_time", new Date())
.add("update_time", new Date())
.add("deleted", false));
return Result.success();
}
private String loginName(String emplid) {
return StrUtil.blankToDefault(emplid, SecurityUtil.getUserLoginname());
}
private NutMap fetchMap(String sqlText, NutMap params) {
Sql sql = Sqls.create(sqlText);
params.forEach(sql::setParam);
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
return sql.getObject(NutMap.class);
}
private int count(String sqlText, NutMap params) {
Sql sql = Sqls.create(sqlText);
params.forEach(sql::setParam);
sql.setCallback(Sqls.callback.integer());
dao.execute(sql);
return sql.getInt();
}
}
@@ -0,0 +1,509 @@
/*
* 职代会独立模块初始化脚本。
* 说明:
* 1. 所有业务表均使用 congress_ 前缀,避免沿用旧职代会模块。
* 2. H5 菜单权限使用 h5.congress / h5.congress.index。
* 3. sys_menu.path 按当前同级最大 path 自动递增,避免固定 path 导致唯一索引冲突。
*/
CREATE TABLE IF NOT EXISTS `congress_sessions` (
`id` varchar(32) NOT NULL COMMENT '主键',
`name` varchar(255) DEFAULT NULL COMMENT '届次名称',
`year` varchar(32) DEFAULT NULL COMMENT '年份',
`description` varchar(1000) DEFAULT NULL COMMENT '描述',
`sc_session` int DEFAULT NULL COMMENT '省产届次',
`uc_session` int DEFAULT NULL COMMENT '校产届次',
`union_id` varchar(32) DEFAULT NULL COMMENT '工会ID',
`type` int DEFAULT NULL COMMENT '会类型',
`union_name` varchar(255) DEFAULT NULL COMMENT '工会名称',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_sessions_union_id` (`union_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会届次';
CREATE TABLE IF NOT EXISTS `congress_times` (
`id` varchar(32) NOT NULL COMMENT '主键',
`year` varchar(32) DEFAULT NULL COMMENT '年份',
`name` varchar(255) DEFAULT NULL COMMENT '会议名称',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`session_name` varchar(255) DEFAULT NULL COMMENT '届次名称',
`sc_times` int DEFAULT NULL COMMENT '省产次数',
`uc_times` int DEFAULT NULL COMMENT '校产次数',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_times_session_id` (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会会议';
CREATE TABLE IF NOT EXISTS `congress_delegation` (
`id` varchar(32) NOT NULL COMMENT '主键',
`name` varchar(255) DEFAULT NULL COMMENT '代表团名称',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`session_name` varchar(255) DEFAULT NULL COMMENT '届次名称',
`time_id` varchar(32) DEFAULT NULL COMMENT '会议ID',
`time_name` varchar(255) DEFAULT NULL COMMENT '会议名称',
`code` varchar(64) DEFAULT NULL COMMENT '编码',
`official_number` int DEFAULT '0' COMMENT '正式代表人数',
`attendance_number` int DEFAULT '0' COMMENT '列席代表人数',
`invite_number` int DEFAULT '0' COMMENT '特邀代表人数',
`committee_allot_number` int DEFAULT '0' COMMENT '委员分配人数',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_delegation_time` (`time_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会代表团';
CREATE TABLE IF NOT EXISTS `congress_delegation_head` (
`id` varchar(32) NOT NULL COMMENT '主键',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`time_id` varchar(32) DEFAULT NULL COMMENT '会议ID',
`delegation_id` varchar(32) DEFAULT NULL COMMENT '代表团ID',
`user_id` varchar(32) DEFAULT NULL COMMENT '用户ID',
`emplid` varchar(64) DEFAULT NULL COMMENT '工号',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`unit` varchar(255) DEFAULT NULL COMMENT '单位',
`mobile` varchar(64) DEFAULT NULL COMMENT '电话',
`sex` int DEFAULT NULL COMMENT '性别',
`identity` int DEFAULT NULL COMMENT '身份',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_delegation_head_delegation` (`delegation_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会代表团团长';
CREATE TABLE IF NOT EXISTS `congress_delegation_rep` (
`id` varchar(32) NOT NULL COMMENT '主键',
`delegation_id` varchar(32) DEFAULT NULL COMMENT '代表团ID',
`user_id` varchar(32) DEFAULT NULL COMMENT '用户ID',
`empid` varchar(64) DEFAULT NULL COMMENT '工号',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`unit` varchar(255) DEFAULT NULL COMMENT '单位',
`mobile` varchar(64) DEFAULT NULL COMMENT '电话',
`sex` int DEFAULT NULL COMMENT '性别',
`identity` int DEFAULT NULL COMMENT '身份',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_delegation_rep_delegation` (`delegation_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会代表团代表';
CREATE TABLE IF NOT EXISTS `congress_delegation_union` (
`id` varchar(32) NOT NULL COMMENT '主键',
`delegation_id` varchar(32) DEFAULT NULL COMMENT '代表团ID',
`time_id` varchar(32) DEFAULT NULL COMMENT '会议ID',
`union_id` varchar(32) DEFAULT NULL COMMENT '工会ID',
`union_name` varchar(255) DEFAULT NULL COMMENT '工会名称',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_delegation_union_delegation` (`delegation_id`),
KEY `idx_congress_delegation_union_time` (`time_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会代表团工会';
CREATE TABLE IF NOT EXISTS `congress_union` (
`id` varchar(32) NOT NULL COMMENT '主键',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`session_name` varchar(255) DEFAULT NULL COMMENT '届次名称',
`union_id` varchar(32) DEFAULT NULL COMMENT '工会ID',
`union_name` varchar(255) DEFAULT NULL COMMENT '工会名称',
`quota_config` text COMMENT '名额配置',
`member_number` int DEFAULT '0' COMMENT '会员数',
`delegation_id` varchar(32) DEFAULT NULL COMMENT '代表团ID',
`sort_no` int DEFAULT '0' COMMENT '排序',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_union_session` (`session_id`),
KEY `idx_congress_union_delegation` (`delegation_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会工会名额';
CREATE TABLE IF NOT EXISTS `congress_rep_supplement` (
`id` varchar(32) NOT NULL COMMENT '主键',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`precinct_union_id` varchar(32) DEFAULT NULL COMMENT '选区',
`time_id` varchar(32) DEFAULT NULL COMMENT '会议ID',
`time_name` varchar(255) DEFAULT NULL COMMENT '会议名称',
`add_user_id` varchar(32) DEFAULT NULL COMMENT '增加代表用户ID',
`add_user_union_id` varchar(32) DEFAULT NULL COMMENT '增加代表工会ID',
`add_user_name` varchar(100) DEFAULT NULL COMMENT '增加代表姓名',
`add_emplid` varchar(64) DEFAULT NULL COMMENT '增加代表工号',
`sub_user_id` varchar(32) DEFAULT NULL COMMENT '替换代表用户ID',
`sub_user_name` varchar(100) DEFAULT NULL COMMENT '替换代表姓名',
`sub_emplid` varchar(64) DEFAULT NULL COMMENT '替换代表工号',
`reason` varchar(1000) DEFAULT NULL COMMENT '原因',
`add_precinct_union_id` varchar(32) DEFAULT NULL COMMENT '增加选区工会ID',
`add_precinct_union_name` varchar(255) DEFAULT NULL COMMENT '增加选区工会名',
`sub_precinct_union_id` varchar(32) DEFAULT NULL COMMENT '替换选区工会ID',
`sub_precinct_union_name` varchar(255) DEFAULT NULL COMMENT '替换选区工会名',
`type` int DEFAULT NULL COMMENT '类型',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_rep_supplement_time` (`time_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会代表增补';
CREATE TABLE IF NOT EXISTS `congress_org` (
`id` varchar(32) NOT NULL COMMENT '主键',
`name` varchar(255) DEFAULT NULL COMMENT '机构名称',
`code` varchar(64) DEFAULT NULL COMMENT '机构代码',
`type` varchar(64) DEFAULT NULL COMMENT '机构类别',
`remark` varchar(1000) DEFAULT NULL COMMENT '备注',
`parent_id` varchar(32) DEFAULT NULL COMMENT '父节点',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`session_name` varchar(255) DEFAULT NULL COMMENT '届次名称',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_org_session` (`session_id`),
KEY `idx_congress_org_parent` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会机构';
CREATE TABLE IF NOT EXISTS `congress_org_member` (
`id` varchar(32) NOT NULL COMMENT '主键',
`user_id` varchar(32) DEFAULT NULL COMMENT '用户ID',
`empid` varchar(64) DEFAULT NULL COMMENT '工号',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`unit` varchar(255) DEFAULT NULL COMMENT '单位',
`mobile` varchar(64) DEFAULT NULL COMMENT '电话',
`sex` int DEFAULT NULL COMMENT '性别',
`identity` int DEFAULT NULL COMMENT '身份',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`session_name` varchar(255) DEFAULT NULL COMMENT '届次名称',
`org_id` varchar(32) DEFAULT NULL COMMENT '机构ID',
`role_id` varchar(32) DEFAULT NULL COMMENT '角色ID',
`role_name` varchar(100) DEFAULT NULL COMMENT '角色名称',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_org_member_org` (`org_id`),
KEY `idx_congress_org_member_session` (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会机构成员';
CREATE TABLE IF NOT EXISTS `congress_committee_elect_quota` (
`id` varchar(32) NOT NULL COMMENT '主键',
`zbztjmes` int DEFAULT '0' COMMENT '筹备组推荐名额数',
`wyhyxrs` int DEFAULT '0' COMMENT '委员会预选人数',
`dbttjzs` int DEFAULT '0' COMMENT '代表团推荐总数',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`session_name` varchar(255) DEFAULT NULL COMMENT '届次名称',
`first_preselected_begin_time` datetime DEFAULT NULL COMMENT '第一次预选开始时间',
`first_preselected_end_time` datetime DEFAULT NULL COMMENT '第一次预选结束时间',
`second_preselected_begin_time` datetime DEFAULT NULL COMMENT '第二次预选开始时间',
`second_preselected_end_time` datetime DEFAULT NULL COMMENT '第二次预选结束时间',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_committee_elect_quota_session` (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会委员预选分配';
CREATE TABLE IF NOT EXISTS `congress_committee_elect_rep` (
`id` varchar(32) NOT NULL COMMENT '主键',
`rep_id` varchar(32) DEFAULT NULL COMMENT '代表ID',
`type` int DEFAULT NULL COMMENT '推选类型',
`times` int DEFAULT NULL COMMENT '第几次',
`user_id` varchar(32) DEFAULT NULL COMMENT '团长用户ID',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`time_id` varchar(32) DEFAULT NULL COMMENT '会议ID',
`delegation_id` varchar(32) DEFAULT NULL COMMENT '代表团ID',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_committee_elect_rep_session` (`session_id`),
KEY `idx_congress_committee_elect_rep_delegation` (`delegation_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会委员推选';
CREATE TABLE IF NOT EXISTS `congress_rep` (
`id` varchar(32) NOT NULL COMMENT '主键',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`time_id` varchar(32) DEFAULT NULL COMMENT '会议ID',
`session_name` varchar(255) DEFAULT NULL COMMENT '届次名称',
`time_name` varchar(255) DEFAULT NULL COMMENT '会议名称',
`user_id` varchar(32) DEFAULT NULL COMMENT '用户ID',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`emplid` varchar(64) DEFAULT NULL COMMENT '工号',
`sex` varchar(16) DEFAULT NULL COMMENT '性别',
`birthdate` varchar(32) DEFAULT NULL COMMENT '出生日期',
`politics_status` varchar(100) DEFAULT NULL COMMENT '政治面貌',
`position` varchar(255) DEFAULT NULL COMMENT '职务',
`education` varchar(100) DEFAULT NULL COMMENT '学历',
`work_unit` varchar(255) DEFAULT NULL COMMENT '工作单位',
`work_experience` text COMMENT '工作经历',
`tags` varchar(255) DEFAULT NULL COMMENT '标签',
`filling_status` varchar(32) DEFAULT NULL COMMENT '填报状态',
`identity` varchar(64) DEFAULT NULL COMMENT '身份',
`type` varchar(64) DEFAULT NULL COMMENT '代表类型',
`is_gdh` tinyint(1) DEFAULT '0' COMMENT '是否工代会代表',
`is_zdh` tinyint(1) DEFAULT '0' COMMENT '是否职代会代表',
`union_id` varchar(32) DEFAULT NULL COMMENT '工会ID',
`delegation_id` varchar(32) DEFAULT NULL COMMENT '代表团ID',
`delegation_name` varchar(255) DEFAULT NULL COMMENT '代表团名称',
`supplement` text COMMENT '补充说明',
`status` int DEFAULT '0' COMMENT '状态',
`precinct_union_id` varchar(32) DEFAULT NULL COMMENT '选区工会ID',
`precinct_union_name` varchar(255) DEFAULT NULL COMMENT '选区工会名称',
`nation` varchar(64) DEFAULT NULL COMMENT '民族',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_rep_emplid` (`emplid`),
KEY `idx_congress_rep_time_emplid` (`time_id`,`emplid`),
KEY `idx_congress_rep_session_id` (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会代表';
CREATE TABLE IF NOT EXISTS `congress_materials_type` (
`id` varchar(32) NOT NULL COMMENT '主键',
`sort_no` int DEFAULT '0' COMMENT '排序',
`name` varchar(100) DEFAULT NULL COMMENT '名称',
`is_vote` tinyint(1) DEFAULT '0' COMMENT '是否表决类',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会资料类型';
CREATE TABLE IF NOT EXISTS `congress_materials` (
`id` varchar(32) NOT NULL COMMENT '主键',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`time_id` varchar(32) DEFAULT NULL COMMENT '会议ID',
`type_id` varchar(32) DEFAULT NULL COMMENT '资料类型ID',
`sort_no` int DEFAULT '0' COMMENT '排序',
`name` varchar(255) DEFAULT NULL COMMENT '资料名称',
`file_name` varchar(255) DEFAULT NULL COMMENT '文件名',
`file_url` varchar(1000) DEFAULT NULL COMMENT '文件地址',
`file_type` varchar(64) DEFAULT NULL COMMENT '文件类型',
`file_size` varchar(64) DEFAULT NULL COMMENT '文件大小',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_materials_time_type` (`time_id`,`type_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会资料';
CREATE TABLE IF NOT EXISTS `congress_sign_in_meeting` (
`id` varchar(32) NOT NULL COMMENT '主键',
`meeting_name` varchar(255) DEFAULT NULL COMMENT '会议名称',
`sign_in_start_time` datetime DEFAULT NULL COMMENT '签到开始时间',
`sign_in_end_time` datetime DEFAULT NULL COMMENT '签到结束时间',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`time_id` varchar(32) DEFAULT NULL COMMENT '会议ID',
`remark` varchar(1000) DEFAULT NULL COMMENT '备注',
`sign_in_count` int DEFAULT '0' COMMENT '签到人数',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_sign_in_meeting_time` (`time_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会签到会议';
CREATE TABLE IF NOT EXISTS `congress_sign_in` (
`id` varchar(32) NOT NULL COMMENT '主键',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`time_id` varchar(32) DEFAULT NULL COMMENT '会议ID',
`meeting_id` varchar(32) DEFAULT NULL COMMENT '签到会议ID',
`delegation_id` varchar(32) DEFAULT NULL COMMENT '代表团ID',
`rep_id` varchar(32) DEFAULT NULL COMMENT '代表ID',
`emplid` varchar(64) DEFAULT NULL COMMENT '工号',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_congress_sign_in_rep_meeting` (`rep_id`,`meeting_id`,`deleted`),
KEY `idx_congress_sign_in_meeting` (`meeting_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会签到记录';
CREATE TABLE IF NOT EXISTS `congress_voting_issue` (
`id` varchar(32) NOT NULL COMMENT '主键',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`session_name` varchar(255) DEFAULT NULL COMMENT '届次名称',
`time_id` varchar(32) DEFAULT NULL COMMENT '会议ID',
`time_name` varchar(255) DEFAULT NULL COMMENT '会议名称',
`name` varchar(255) DEFAULT NULL COMMENT '议题名称',
`description` varchar(1000) DEFAULT NULL COMMENT '描述',
`content` text COMMENT '内容',
`voting_type` varchar(64) DEFAULT NULL COMMENT '表决类型',
`voting_start_time` datetime DEFAULT NULL COMMENT '表决开始时间',
`voting_end_time` datetime DEFAULT NULL COMMENT '表决结束时间',
`voting_content` text COMMENT '表决内容',
`voting_content_item_header` varchar(255) DEFAULT NULL COMMENT '表决项表头',
`voting_content_option_header` varchar(255) DEFAULT NULL COMMENT '表决选项表头',
`materials_id` varchar(32) DEFAULT NULL COMMENT '资料ID',
`type_id` varchar(32) DEFAULT NULL COMMENT '资料类型ID',
`delegation_id` varchar(32) DEFAULT NULL COMMENT '代表团ID',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_congress_voting_issue_materials` (`materials_id`),
KEY `idx_congress_voting_issue_time` (`time_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会议题表决';
CREATE TABLE IF NOT EXISTS `congress_voting_record` (
`id` varchar(32) NOT NULL COMMENT '主键',
`issue_id` varchar(32) DEFAULT NULL COMMENT '议题ID',
`rep_id` varchar(32) DEFAULT NULL COMMENT '代表ID',
`emplid` varchar(64) DEFAULT NULL COMMENT '工号',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`session_id` varchar(32) DEFAULT NULL COMMENT '届次ID',
`time_id` varchar(32) DEFAULT NULL COMMENT '会议ID',
`delegation_id` varchar(32) DEFAULT NULL COMMENT '代表团ID',
`vote_result` text COMMENT '表决结果',
`user_id` varchar(32) DEFAULT NULL COMMENT '用户ID',
`create_by` varchar(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(32) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`deleted` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_congress_voting_record_rep_issue` (`rep_id`,`issue_id`,`deleted`),
KEY `idx_congress_voting_record_issue` (`issue_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='职代会表决记录';
INSERT INTO `congress_materials_type` (`id`, `sort_no`, `name`, `is_vote`, `create_time`, `update_time`, `deleted`)
SELECT 'congress_type_file_notice', 10, '文件公告', 0, NOW(), NOW(), 0
WHERE NOT EXISTS (SELECT 1 FROM `congress_materials_type` WHERE `id` = 'congress_type_file_notice');
INSERT INTO `congress_materials_type` (`id`, `sort_no`, `name`, `is_vote`, `create_time`, `update_time`, `deleted`)
SELECT 'congress_type_evaluation_vote', 20, '评议表决', 1, NOW(), NOW(), 0
WHERE NOT EXISTS (SELECT 1 FROM `congress_materials_type` WHERE `id` = 'congress_type_evaluation_vote');
INSERT INTO `congress_materials_type` (`id`, `sort_no`, `name`, `is_vote`, `create_time`, `update_time`, `deleted`)
SELECT 'congress_type_issue_vote', 30, '议题表决', 1, NOW(), NOW(), 0
WHERE NOT EXISTS (SELECT 1 FROM `congress_materials_type` WHERE `id` = 'congress_type_issue_vote');
INSERT INTO `congress_materials_type` (`id`, `sort_no`, `name`, `is_vote`, `create_time`, `update_time`, `deleted`)
SELECT 'congress_type_vote_notice', 40, '表决公告', 0, NOW(), NOW(), 0
WHERE NOT EXISTS (SELECT 1 FROM `congress_materials_type` WHERE `id` = 'congress_type_vote_notice');
INSERT INTO `congress_materials_type` (`id`, `sort_no`, `name`, `is_vote`, `create_time`, `update_time`, `deleted`)
SELECT 'congress_type_prepare_material', 50, '准备资料', 0, NOW(), NOW(), 0
WHERE NOT EXISTS (SELECT 1 FROM `congress_materials_type` WHERE `id` = 'congress_type_prepare_material');
INSERT INTO `sys_menu` (`id`, `parentId`, `path`, `name`, `aliasName`, `type`, `href`, `target`, `icon`, `showit`, `disabled`, `permission`, `note`, `location`, `hasChildren`, `createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`, `platform`, `moduleId`, `picIcon`, `initialPinyinName`, `isRecommendApp`, `isRecommendService`)
SELECT
'congress_h5',
'',
LPAD(IFNULL(MAX(CAST(`path` AS UNSIGNED)), 0) + 1, 4, '0'),
'职代会',
'Congress',
'menu',
'',
'_self',
'records-o',
1,
0,
'h5.congress',
NULL,
980,
1,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
0,
'H5',
NULL,
NULL,
'z',
0,
0
FROM `sys_menu`
WHERE (`parentId` IS NULL OR `parentId` = '')
AND CHAR_LENGTH(`path`) = 4
HAVING NOT EXISTS (
SELECT 1 FROM (SELECT `id` FROM `sys_menu` WHERE `permission` = 'h5.congress') t
);
INSERT INTO `sys_menu` (`id`, `parentId`, `path`, `name`, `aliasName`, `type`, `href`, `target`, `icon`, `showit`, `disabled`, `permission`, `note`, `location`, `hasChildren`, `createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`, `platform`, `moduleId`, `picIcon`, `initialPinyinName`, `isRecommendApp`, `isRecommendService`)
SELECT
'congress_h5_index',
p.`id`,
CONCAT(p.`path`, LPAD(IFNULL(MAX(CAST(SUBSTRING(c.`path`, CHAR_LENGTH(p.`path`) + 1) AS UNSIGNED)), 0) + 1, 4, '0')),
'职代会首页',
'Congress Index',
'menu',
'/platform/h5/congress',
'_self',
'records-o',
1,
0,
'h5.congress.index',
NULL,
1,
0,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
0,
'H5',
NULL,
NULL,
'z',
0,
0
FROM `sys_menu` p
LEFT JOIN `sys_menu` c ON c.`parentId` = p.`id`
WHERE p.`permission` = 'h5.congress'
AND NOT EXISTS (
SELECT 1 FROM (SELECT `id` FROM `sys_menu` WHERE `permission` = 'h5.congress.index') t
)
GROUP BY p.`id`, p.`path`;
INSERT INTO `sys_role_menu` (`roleId`, `menuId`)
SELECT r.`id`, m.`id`
FROM `sys_role` r
INNER JOIN `sys_menu` m ON m.`permission` IN ('h5.congress', 'h5.congress.index')
LEFT JOIN `sys_role_menu` rm ON rm.`roleId` = r.`id` AND rm.`menuId` = m.`id`
WHERE r.`code` = 'SYSADMIN'
AND rm.`roleId` IS NULL;
@@ -0,0 +1,228 @@
-- 职代会电脑端菜单(MySQL 8)。
-- 不包含库名前缀,可重复执行。
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target, icon,
showit, disabled, permission, note, location, hasChildren,
createdBy, createdAt, updatedBy, updatedAt, delFlag, platform,
moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService
)
SELECT
'congress_pc_root', '',
LPAD(IFNULL(MAX(CAST(`path` AS UNSIGNED)), 0) + 1, 4, '0'),
'职代会管理', 'Congress', 'menu', '', '', 'ti-agenda',
1, 0, 'congress', NULL, 1, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000,
'', UNIX_TIMESTAMP(NOW()) * 1000,
0, 'PC', NULL, NULL, 'z', 0, 0
FROM sys_menu
WHERE (parentId IS NULL OR parentId = '') AND CHAR_LENGTH(path) = 4
HAVING NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'congress') t
);
-- 一级分组:会议资料(排序 6)。
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target, icon,
showit, disabled, permission, note, location, hasChildren,
createdBy, createdAt, updatedBy, updatedAt, delFlag, platform,
moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService
)
SELECT
'congress_pc_materials', p.id, CONCAT(p.path, '0006'),
'会议资料', 'Congress Materials', 'menu', '', '', 'ti-file',
1, 0, 'congress.materials', NULL, 6, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000,
'', UNIX_TIMESTAMP(NOW()) * 1000,
0, 'PC', NULL, NULL, 'h', 0, 0
FROM sys_menu p
WHERE p.permission = 'congress'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'congress.materials') t
);
-- 一级分组:会议签到(排序 7)。
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target, icon,
showit, disabled, permission, note, location, hasChildren,
createdBy, createdAt, updatedBy, updatedAt, delFlag, platform,
moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService
)
SELECT
'congress_pc_signin', p.id, CONCAT(p.path, '0007'),
'会议签到', 'Congress Sign In', 'menu', '', '', 'ti-calendar',
1, 0, 'congress.signIn', NULL, 7, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000,
'', UNIX_TIMESTAMP(NOW()) * 1000,
0, 'PC', NULL, NULL, 'h', 0, 0
FROM sys_menu p
WHERE p.permission = 'congress'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'congress.signIn') t
);
-- 一级分组:会议投票(排序 8)。
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target, icon,
showit, disabled, permission, note, location, hasChildren,
createdBy, createdAt, updatedBy, updatedAt, delFlag, platform,
moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService
)
SELECT
'congress_pc_vote', p.id, CONCAT(p.path, '0008'),
'会议投票', 'Congress Vote', 'menu', '', '', 'ti-bar-chart',
1, 0, 'congress.vote', NULL, 8, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000,
'', UNIX_TIMESTAMP(NOW()) * 1000,
0, 'PC', NULL, NULL, 'h', 0, 0
FROM sys_menu p
WHERE p.permission = 'congress'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'congress.vote') t
);
-- 二级页面菜单。
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target, icon,
showit, disabled, permission, note, location, hasChildren,
createdBy, createdAt, updatedBy, updatedAt, delFlag, platform,
moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService
)
SELECT
'congress_pc_signin_manage', p.id, CONCAT(p.path, '0001'),
'会议管理', 'Meeting Manage', 'menu', '/platform/congress/signIn/index', 'data-pjax', 'ti-layout-grid2',
1, 0, 'congress.signIn.manage', NULL, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000,
'', UNIX_TIMESTAMP(NOW()) * 1000,
0, 'PC', NULL, NULL, 'h', 0, 0
FROM sys_menu p
WHERE p.permission = 'congress.signIn'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'congress.signIn.manage') t
);
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target, icon,
showit, disabled, permission, note, location, hasChildren,
createdBy, createdAt, updatedBy, updatedAt, delFlag, platform,
moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService
)
SELECT
'congress_pc_signin_record', p.id, CONCAT(p.path, '0002'),
'签到记录', 'Sign In Record', 'menu', '/platform/congress/record/index', 'data-pjax', 'ti-layout-grid2',
1, 0, 'congress.signIn.record', NULL, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000,
'', UNIX_TIMESTAMP(NOW()) * 1000,
0, 'PC', NULL, NULL, 'q', 0, 0
FROM sys_menu p
WHERE p.permission = 'congress.signIn'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'congress.signIn.record') t
);
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target, icon,
showit, disabled, permission, note, location, hasChildren,
createdBy, createdAt, updatedBy, updatedAt, delFlag, platform,
moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService
)
SELECT
'congress_pc_vote_issue', p.id, CONCAT(p.path, '0001'),
'投票管理', 'Vote Manage', 'menu', '/platform/congress/materials/issue/index', 'data-pjax', 'ti-menu-alt',
1, 0, 'congress.vote.issue', NULL, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000,
'', UNIX_TIMESTAMP(NOW()) * 1000,
0, 'PC', NULL, NULL, 't', 0, 0
FROM sys_menu p
WHERE p.permission = 'congress.vote'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'congress.vote.issue') t
);
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target, icon,
showit, disabled, permission, note, location, hasChildren,
createdBy, createdAt, updatedBy, updatedAt, delFlag, platform,
moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService
)
SELECT
'congress_pc_vote_statistics', p.id, CONCAT(p.path, '0002'),
'投票统计', 'Vote Statistics', 'menu', '/platform/congress/materials/statistical/index', 'data-pjax', 'ti-menu-alt',
1, 0, 'congress.vote.statistical', NULL, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000,
'', UNIX_TIMESTAMP(NOW()) * 1000,
0, 'PC', NULL, NULL, 't', 0, 0
FROM sys_menu p
WHERE p.permission = 'congress.vote'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'congress.vote.statistical') t
);
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target, icon,
showit, disabled, permission, note, location, hasChildren,
createdBy, createdAt, updatedBy, updatedAt, delFlag, platform,
moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService
)
SELECT
'congress_pc_materials_manage', p.id, CONCAT(p.path, '0002'),
'资料管理', 'Materials Manage', 'menu', '/platform/congress/materials/manage/index', 'data-pjax', 'ti-file',
1, 0, 'congress.materials.manage', NULL, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000,
'', UNIX_TIMESTAMP(NOW()) * 1000,
0, 'PC', NULL, NULL, 'z', 0, 0
FROM sys_menu p
WHERE p.permission = 'congress.materials'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'congress.materials.manage') t
);
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target, icon,
showit, disabled, permission, note, location, hasChildren,
createdBy, createdAt, updatedBy, updatedAt, delFlag, platform,
moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService
)
SELECT
'congress_pc_materials_type', p.id, CONCAT(p.path, '0001'),
'资料类型', 'Materials Type', 'menu', '/platform/congress/materials/type/index', 'data-pjax', 'ti-share-alt',
1, 0, 'congress.materials.type', NULL, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000,
'', UNIX_TIMESTAMP(NOW()) * 1000,
0, 'PC', NULL, NULL, 'z', 0, 0
FROM sys_menu p
WHERE p.permission = 'congress.materials'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'congress.materials.type') t
);
-- 兼容已执行过旧版脚本的数据:根菜单和分组菜单只负责展开,不请求后台地址。
UPDATE sys_menu
SET href = '',
target = '',
updatedBy = '',
updatedAt = UNIX_TIMESTAMP(NOW()) * 1000
WHERE id IN (
'congress_pc_root',
'congress_pc_materials',
'congress_pc_signin',
'congress_pc_vote'
);
-- 系统管理员默认拥有模块菜单,其他角色在角色管理中按需授权。
INSERT INTO sys_role_menu (roleId, menuId)
SELECT r.id, m.id
FROM sys_role r
JOIN sys_menu m ON m.permission IN (
'congress',
'congress.signIn',
'congress.signIn.manage',
'congress.signIn.record',
'congress.vote',
'congress.vote.issue',
'congress.vote.statistical',
'congress.materials',
'congress.materials.manage',
'congress.materials.type'
)
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
WHERE r.code = 'SYSADMIN' AND rm.roleId IS NULL;
@@ -0,0 +1,437 @@
/*
* 职代会 H5 全流程测试数据。
*
* 使用方式:
* 1. 将 @test_emplid 改成当前登录账号的 loginname/工号。
* 2. 先执行 init_congress_module.sql 建表和菜单,再执行本脚本。
* 3. 访问 /platform/h5/congress 测试 H5 职代会入口。
*/
SET @test_emplid = '053181';
SET @test_user_id = (
SELECT id
FROM sys_user
WHERE loginname = @test_emplid
LIMIT 1
);
-- 给当前账号所属角色授权 H5 职代会菜单,保证“全部应用”可见。
INSERT INTO sys_role_menu (roleId, menuId)
SELECT ur.roleId, m.id
FROM sys_user_role ur
JOIN sys_menu m ON m.id IN ('congress_h5', 'congress_h5_index')
LEFT JOIN sys_role_menu rm ON rm.roleId = ur.roleId AND rm.menuId = m.id
WHERE ur.userId = @test_user_id
AND rm.roleId IS NULL;
-- 清理本脚本铺过的测试数据,方便反复执行。
DELETE FROM congress_voting_record WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_voting_issue WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_sign_in WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_sign_in_meeting WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_materials WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_voting_record WHERE id LIKE 'congress_test_%' OR issue_id LIKE 'congress_test_%';
DELETE FROM congress_voting_issue WHERE id LIKE 'congress_test_%' OR session_id LIKE 'congress_test_%' OR time_id LIKE 'congress_test_%' OR materials_id LIKE 'congress_test_%';
DELETE FROM congress_sign_in WHERE id LIKE 'congress_test_%' OR session_id LIKE 'congress_test_%' OR time_id LIKE 'congress_test_%' OR meeting_id LIKE 'congress_test_%';
DELETE FROM congress_sign_in_meeting WHERE id LIKE 'congress_test_%' OR session_id LIKE 'congress_test_%' OR time_id LIKE 'congress_test_%';
DELETE FROM congress_materials WHERE id LIKE 'congress_test_%' OR session_id LIKE 'congress_test_%' OR time_id LIKE 'congress_test_%';
DELETE FROM congress_rep WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%' OR id = CONCAT('congress_rep_', @test_emplid);
DELETE FROM congress_delegation_rep WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_delegation_head WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_delegation_union WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_delegation WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_union WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_delegation_rep WHERE id LIKE 'congress_test_%' OR delegation_id LIKE 'congress_test_%';
DELETE FROM congress_delegation_head WHERE id LIKE 'congress_test_%' OR session_id LIKE 'congress_test_%' OR time_id LIKE 'congress_test_%' OR delegation_id LIKE 'congress_test_%';
DELETE FROM congress_delegation_union WHERE id LIKE 'congress_test_%' OR delegation_id LIKE 'congress_test_%' OR time_id LIKE 'congress_test_%';
DELETE FROM congress_delegation WHERE id LIKE 'congress_test_%' OR session_id LIKE 'congress_test_%' OR time_id LIKE 'congress_test_%';
DELETE FROM congress_union WHERE id LIKE 'congress_test_%' OR session_id LIKE 'congress_test_%' OR delegation_id LIKE 'congress_test_%';
DELETE FROM congress_org_member WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_org WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_committee_elect_rep WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_committee_elect_quota WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_rep_supplement WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_times WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_sessions WHERE id LIKE 'congress_h5_test_%' OR id LIKE 'c_h5_%';
DELETE FROM congress_org_member WHERE id LIKE 'congress_test_%' OR session_id LIKE 'congress_test_%' OR org_id LIKE 'congress_test_%';
DELETE FROM congress_org WHERE id LIKE 'congress_test_%' OR session_id LIKE 'congress_test_%';
DELETE FROM congress_committee_elect_rep WHERE id LIKE 'congress_test_%' OR session_id LIKE 'congress_test_%' OR time_id LIKE 'congress_test_%' OR delegation_id LIKE 'congress_test_%';
DELETE FROM congress_committee_elect_quota WHERE id LIKE 'congress_test_%' OR session_id LIKE 'congress_test_%';
DELETE FROM congress_rep_supplement WHERE id LIKE 'congress_test_%' OR session_id LIKE 'congress_test_%' OR time_id LIKE 'congress_test_%';
DELETE FROM congress_times WHERE id LIKE 'congress_test_%';
DELETE FROM congress_sessions WHERE id LIKE 'congress_test_%';
-- H5 首页模块类型:覆盖会议签到以外的所有分支。
INSERT INTO congress_materials_type (id, sort_no, name, is_vote, create_time, update_time, deleted)
VALUES
('congress_type_file_notice', 10, '文件公告', 0, NOW(), NOW(), 0),
('congress_type_evaluation_vote', 20, '评议表决', 1, NOW(), NOW(), 0),
('congress_type_issue_vote', 30, '议题表决', 1, NOW(), NOW(), 0),
('congress_type_vote_notice', 40, '表决公告', 0, NOW(), NOW(), 0),
('congress_type_prepare_material', 50, '准备资料', 0, NOW(), NOW(), 0)
ON DUPLICATE KEY UPDATE
sort_no = VALUES(sort_no),
name = VALUES(name),
is_vote = VALUES(is_vote),
deleted = 0,
update_time = NOW();
-- 届次/会议。
INSERT INTO congress_sessions (
id, name, year, description, sc_session, uc_session, union_id, type, union_name,
create_time, update_time, deleted
) VALUES (
'c_h5_s_2026',
'职代会H5测试届次',
'2026',
'用于验证职代会H5全流程',
1,
1,
'c_h5_union',
1,
'中国地质大学',
NOW(),
NOW(),
0
);
INSERT INTO congress_times (
id, year, name, session_id, session_name, sc_times, uc_times,
create_time, update_time, deleted
) VALUES (
'c_h5_t_01',
'2026',
'第1次职代会',
'c_h5_s_2026',
'职代会H5测试届次',
1,
1,
NOW(),
NOW(),
0
);
-- 代表团/工会/组织侧基础数据,供后续管理端或关联查询测试。
INSERT INTO congress_delegation (
id, name, session_id, session_name, time_id, time_name, code,
official_number, attendance_number, invite_number, committee_allot_number,
create_time, update_time, deleted
) VALUES (
'c_h5_d_01',
'第一代表团',
'c_h5_s_2026',
'职代会H5测试届次',
'c_h5_t_01',
'第1次职代会',
'D001',
1,
1,
1,
1,
NOW(),
NOW(),
0
);
INSERT INTO congress_union (
id, session_id, session_name, union_id, union_name, quota_config,
member_number, delegation_id, sort_no, create_time, update_time, deleted
) VALUES (
'c_h5_urow_01',
'c_h5_s_2026',
'职代会H5测试届次',
'c_h5_union',
'测试分工会',
'{"officialNumber":1,"attendanceNumber":1,"inviteNumber":1}',
100,
'c_h5_d_01',
1,
NOW(),
NOW(),
0
);
INSERT INTO congress_delegation_union (
id, delegation_id, time_id, union_id, union_name,
create_time, update_time, deleted
) VALUES (
'c_h5_du_01',
'c_h5_d_01',
'c_h5_t_01',
'c_h5_union',
'测试分工会',
NOW(),
NOW(),
0
);
INSERT INTO congress_org (
id, name, code, type, remark, parent_id, session_id, session_name,
create_time, update_time, deleted
) VALUES (
'c_h5_org_01',
'职代会测试委员会',
'ORG001',
'committee',
'H5测试组织',
'',
'c_h5_s_2026',
'职代会H5测试届次',
NOW(),
NOW(),
0
);
INSERT INTO congress_org_member (
id, user_id, empid, name, unit, mobile, sex, identity,
session_id, session_name, org_id, role_id, role_name,
create_time, update_time, deleted
) VALUES (
'c_h5_om_01',
@test_user_id,
@test_emplid,
@test_emplid,
'测试单位',
'13800000000',
1,
1,
'c_h5_s_2026',
'职代会H5测试届次',
'c_h5_org_01',
'c_h5_role_01',
'委员',
NOW(),
NOW(),
0
);
INSERT INTO congress_committee_elect_quota (
id, zbztjmes, wyhyxrs, dbttjzs, session_id, session_name,
first_preselected_begin_time, first_preselected_end_time,
second_preselected_begin_time, second_preselected_end_time,
create_time, update_time, deleted
) VALUES (
'c_h5_cq_01',
1,
1,
1,
'c_h5_s_2026',
'职代会H5测试届次',
DATE_SUB(NOW(), INTERVAL 1 DAY),
DATE_ADD(NOW(), INTERVAL 7 DAY),
DATE_ADD(NOW(), INTERVAL 8 DAY),
DATE_ADD(NOW(), INTERVAL 14 DAY),
NOW(),
NOW(),
0
);
-- 当前账号设为正式代表。status=2 是 H5 首页能查到会议的关键。
INSERT INTO congress_rep (
id, session_id, time_id, session_name, time_name, user_id, name, emplid,
sex, birthdate, politics_status, position, education, work_unit, work_experience,
tags, filling_status, identity, type, is_gdh, is_zdh, union_id,
delegation_id, delegation_name, supplement, status, precinct_union_id,
precinct_union_name, nation, create_time, update_time, deleted
) VALUES (
'c_h5_rep_01',
'c_h5_s_2026',
'c_h5_t_01',
'职代会H5测试届次',
'第1次职代会',
@test_user_id,
@test_emplid,
@test_emplid,
'',
'1990-01-01',
'群众',
'测试人员',
'本科',
'测试单位',
'用于H5职代会测试',
'正式代表',
'已提交',
'正式代表',
'正式代表',
0,
1,
'c_h5_union',
'c_h5_d_01',
'第一代表团',
'H5测试代表',
2,
'c_h5_union',
'测试分工会',
'汉族',
NOW(),
NOW(),
0
);
INSERT INTO congress_delegation_rep (
id, delegation_id, user_id, empid, name, unit, mobile, sex, identity,
create_time, update_time, deleted
) VALUES (
'c_h5_dr_01',
'c_h5_d_01',
@test_user_id,
@test_emplid,
@test_emplid,
'测试单位',
'13800000000',
1,
1,
NOW(),
NOW(),
0
);
INSERT INTO congress_delegation_head (
id, session_id, time_id, delegation_id, user_id, emplid, name,
unit, mobile, sex, identity, create_time, update_time, deleted
) VALUES (
'c_h5_dh_01',
'c_h5_s_2026',
'c_h5_t_01',
'c_h5_d_01',
@test_user_id,
@test_emplid,
@test_emplid,
'测试单位',
'13800000000',
1,
1,
NOW(),
NOW(),
0
);
INSERT INTO congress_committee_elect_rep (
id, rep_id, type, times, user_id, session_id, time_id, delegation_id,
create_time, update_time, deleted
) VALUES (
'c_h5_cr_01',
'c_h5_rep_01',
1,
1,
@test_user_id,
'c_h5_s_2026',
'c_h5_t_01',
'c_h5_d_01',
NOW(),
NOW(),
0
);
INSERT INTO congress_rep_supplement (
id, session_id, precinct_union_id, time_id, time_name,
add_user_id, add_user_union_id, add_user_name, add_emplid,
sub_user_id, sub_user_name, sub_emplid, reason,
add_precinct_union_id, add_precinct_union_name,
sub_precinct_union_id, sub_precinct_union_name, type,
create_time, update_time, deleted
) VALUES (
'c_h5_rs_01',
'c_h5_s_2026',
'c_h5_union',
'c_h5_t_01',
'第1次职代会',
@test_user_id,
'c_h5_union',
@test_emplid,
@test_emplid,
NULL,
NULL,
NULL,
'H5测试增补记录',
'c_h5_union',
'测试分工会',
NULL,
NULL,
1,
NOW(),
NOW(),
0
);
-- 签到列表:覆盖可签到、已签到、未开始、已结束四种状态。
INSERT INTO congress_sign_in_meeting (
id, meeting_name, sign_in_start_time, sign_in_end_time,
session_id, time_id, remark, sign_in_count, create_time, update_time, deleted
) VALUES
('c_h5_m_open', '测试会议-可签到', DATE_SUB(NOW(), INTERVAL 1 DAY), DATE_ADD(NOW(), INTERVAL 7 DAY), 'c_h5_s_2026', 'c_h5_t_01', '点击后应可签到', 0, NOW(), NOW(), 0),
('c_h5_m_signed', '测试会议-已签到', DATE_SUB(NOW(), INTERVAL 1 DAY), DATE_ADD(NOW(), INTERVAL 7 DAY), 'c_h5_s_2026', 'c_h5_t_01', '应显示已签到时间', 1, NOW(), NOW(), 0),
('c_h5_m_future', '测试会议-未到签到时间', DATE_ADD(NOW(), INTERVAL 1 DAY), DATE_ADD(NOW(), INTERVAL 7 DAY), 'c_h5_s_2026', 'c_h5_t_01', '按钮应显示未到签到时间', 0, NOW(), NOW(), 0),
('c_h5_m_expired', '测试会议-已超过签到时间', DATE_SUB(NOW(), INTERVAL 7 DAY), DATE_SUB(NOW(), INTERVAL 1 DAY), 'c_h5_s_2026', 'c_h5_t_01', '按钮应显示已超过签到时间', 0, NOW(), NOW(), 0);
INSERT INTO congress_sign_in (
id, session_id, time_id, meeting_id, delegation_id, rep_id,
emplid, name, create_time, update_time, deleted
) VALUES (
'c_h5_si_signed',
'c_h5_s_2026',
'c_h5_t_01',
'c_h5_m_signed',
'c_h5_d_01',
'c_h5_rep_01',
@test_emplid,
@test_emplid,
DATE_SUB(NOW(), INTERVAL 2 HOUR),
NOW(),
0
);
-- 资料列表:覆盖所有 H5 模块。
INSERT INTO congress_materials (
id, session_id, time_id, type_id, sort_no, name,
file_name, file_url, file_type, file_size,
create_time, update_time, deleted
) VALUES
('c_h5_mat_file_01', 'c_h5_s_2026', 'c_h5_t_01', 'congress_type_file_notice', 10, '文件公告-会议须知', '会议须知.pdf', '/assets/platform/img/home/avatar_boy.png', 'image/png', '12KB', NOW(), NOW(), 0),
('c_h5_mat_file_02', 'c_h5_s_2026', 'c_h5_t_01', 'congress_type_file_notice', 20, '文件公告-会议议程', '会议议程.pdf', '/assets/platform/img/home/avatar_girl.png', 'image/png', '10KB', NOW(), NOW(), 0),
('c_h5_mat_eval_p', 'c_h5_s_2026', 'c_h5_t_01', 'congress_type_evaluation_vote', 10, '技术保障说明.pdf', '技术保障说明.pdf', '', 'pdf', '1KB', NOW(), NOW(), 0),
('c_h5_mat_eval_d', 'c_h5_s_2026', 'c_h5_t_01', 'congress_type_evaluation_vote', 20, '技术保障说明-已投票.pdf', '技术保障说明-已投票.pdf', '', 'pdf', '1KB', NOW(), NOW(), 0),
('c_h5_mat_issue_p', 'c_h5_s_2026', 'c_h5_t_01', 'congress_type_issue_vote', 10, '议题表决-待投票', '议题表决-待投票.pdf', '', 'pdf', '1KB', NOW(), NOW(), 0),
('c_h5_mat_issue_d', 'c_h5_s_2026', 'c_h5_t_01', 'congress_type_issue_vote', 20, '议题表决-已投票', '议题表决-已投票.pdf', '', 'pdf', '1KB', NOW(), NOW(), 0),
('c_h5_mat_notice', 'c_h5_s_2026', 'c_h5_t_01', 'congress_type_vote_notice', 10, '表决公告-结果公告', '表决结果公告.pdf', '/assets/platform/img/home/avatar_boy.png', 'image/png', '8KB', NOW(), NOW(), 0),
('c_h5_mat_prepare', 'c_h5_s_2026', 'c_h5_t_01', 'congress_type_prepare_material', 10, '准备资料-会议材料汇编', '会议材料汇编.pdf', '/assets/platform/img/home/avatar_girl.png', 'image/png', '20KB', NOW(), NOW(), 0);
-- 投票议题:覆盖待投票和已投票。
INSERT INTO congress_voting_issue (
id, session_id, session_name, time_id, time_name,
name, description, content, voting_type,
voting_start_time, voting_end_time, voting_content,
voting_content_item_header, voting_content_option_header,
materials_id, type_id, delegation_id,
create_time, update_time, deleted
) VALUES
('c_h5_issue_eval_p', 'c_h5_s_2026', '王西光测试', 'c_h5_t_01', '整行七届一次职代会', '上海浦东发展银行七届一次评议表决', '上海浦东发展银行七届一次评议表决用于验证职代会评议表决投票说明展示,测试内容需在移动端按浦发样式显示。', '评议表决说明', 'single', DATE_SUB(NOW(), INTERVAL 1 DAY), DATE_ADD(NOW(), INTERVAL 7 DAY), '[{"itemName":"王西光","options":["满意","基本满意","不够满意","不满意"],"selections":{"满意":false,"基本满意":false,"不够满意":false,"不满意":false}},{"itemName":"杜学成","options":["满意","基本满意","不够满意","不满意"],"selections":{"满意":false,"基本满意":false,"不够满意":false,"不满意":false}}]', '评议监事', '评议内容111', 'c_h5_mat_eval_p', 'congress_type_evaluation_vote', 'c_h5_d_01', NOW(), NOW(), 0),
('c_h5_issue_eval_d', 'c_h5_s_2026', '王西光测试', 'c_h5_t_01', '整行七届一次职代会', '上海浦东发展银行七届一次评议表决', '上海浦东发展银行七届一次评议表决用于验证职代会已投票结果展示,测试内容需在移动端按浦发样式显示。', '评议表决说明', 'single', DATE_SUB(NOW(), INTERVAL 1 DAY), DATE_ADD(NOW(), INTERVAL 7 DAY), '[{"itemName":"王西光","options":["满意","基本满意","不够满意","不满意"],"selections":{"满意":false,"基本满意":false,"不够满意":false,"不满意":false}},{"itemName":"杜学成","options":["满意","基本满意","不够满意","不满意"],"selections":{"满意":false,"基本满意":false,"不够满意":false,"不满意":false}}]', '评议监事', '评议内容111', 'c_h5_mat_eval_d', 'congress_type_evaluation_vote', 'c_h5_d_01', NOW(), NOW(), 0),
('c_h5_issue_topic_p', 'c_h5_s_2026', '职代会H5测试届次', 'c_h5_t_01', '第1次职代会', '议题表决-待投票', '用于测试议题表决提交', '议题表决说明', 'single', DATE_SUB(NOW(), INTERVAL 1 DAY), DATE_ADD(NOW(), INTERVAL 7 DAY), '[{"itemName":"是否同意该议题","options":["赞成","不赞成","弃权"]}]', '议题事项', '表决选项', 'c_h5_mat_issue_p', 'congress_type_issue_vote', 'c_h5_d_01', NOW(), NOW(), 0),
('c_h5_issue_topic_d', 'c_h5_s_2026', '职代会H5测试届次', 'c_h5_t_01', '第1次职代会', '议题表决-已投票', '用于测试已投票详情页', '议题表决说明', 'single', DATE_SUB(NOW(), INTERVAL 1 DAY), DATE_ADD(NOW(), INTERVAL 7 DAY), '[{"itemName":"是否同意已投票议题","options":["赞成","不赞成","弃权"]}]', '议题事项', '表决选项', 'c_h5_mat_issue_d', 'congress_type_issue_vote', 'c_h5_d_01', NOW(), NOW(), 0);
INSERT INTO congress_voting_record (
id, issue_id, rep_id, emplid, name, session_id, time_id,
delegation_id, vote_result, user_id, create_time, update_time, deleted
) VALUES
('c_h5_vr_eval_d', 'c_h5_issue_eval_d', 'c_h5_rep_01', @test_emplid, @test_emplid, 'c_h5_s_2026', 'c_h5_t_01', 'c_h5_d_01', '[{"item":"王西光","result":"满意"},{"item":"杜学成","result":"满意"}]', @test_user_id, DATE_SUB(NOW(), INTERVAL 1 HOUR), NOW(), 0),
('c_h5_vr_topic_d', 'c_h5_issue_topic_d', 'c_h5_rep_01', @test_emplid, @test_emplid, 'c_h5_s_2026', 'c_h5_t_01', 'c_h5_d_01', '[{"item":"是否同意已投票议题","result":"弃权"}]', @test_user_id, DATE_SUB(NOW(), INTERVAL 1 HOUR), NOW(), 0);
-- 如果库里历史上存在同名资料类型,H5 类型接口会优先返回下面这些标准 ID。
-- 这里再次校正测试资料绑定,避免点击“评议表决/议题表决”后按 typeId 查询为空。
UPDATE congress_materials
SET type_id = 'congress_type_evaluation_vote'
WHERE id IN ('c_h5_mat_eval_p', 'c_h5_mat_eval_d');
UPDATE congress_voting_issue
SET type_id = 'congress_type_evaluation_vote'
WHERE id IN ('c_h5_issue_eval_p', 'c_h5_issue_eval_d');
UPDATE congress_materials
SET type_id = 'congress_type_issue_vote'
WHERE id IN ('c_h5_mat_issue_p', 'c_h5_mat_issue_d');
UPDATE congress_voting_issue
SET type_id = 'congress_type_issue_vote'
WHERE id IN ('c_h5_issue_topic_p', 'c_h5_issue_topic_d');
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

@@ -261,7 +261,7 @@
// PJAX 替换 #container 时可能已经移除旧样式节点,清理前需兼容节点脱离 DOM 的情况。
if (window.customStyleList) {
window.customStyleList.forEach((style) => {
if (style.parentNode) {
if (style && style.parentNode) {
style.parentNode.removeChild(style)
}
})
@@ -0,0 +1,370 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="届次">
<el-select v-model="pageForm.sessionId" clearable filterable placeholder="请选择届次">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="会议名称">
<el-input v-model.trim="pageForm.name" clearable placeholder="请输入会议名称"></el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="投票管理">
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAdd">新增</el-button>
</table-tool>
<el-table :data="tableData" v-loading="tableLoading" class="vi-table">
<el-table-column type="index" :index="indexMethod" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="name" label="会议名称" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column label="届次" min-width="220">
<template slot-scope="{row}">{{row.sessionName}}</template>
</el-table-column>
<el-table-column prop="typeName" label="投票类型" min-width="130"></el-table-column>
<el-table-column prop="materialsName" label="投票资料" min-width="180" show-overflow-tooltip>
<template slot-scope="{row}">
<el-link v-if="row.fileUrl" type="primary" :href="row.fileUrl" target="_blank">
{{row.materialsName}}
</el-link>
<span v-else>{{row.materialsName || "-"}}</span>
</template>
</el-table-column>
<el-table-column label="投票时间" min-width="300">
<template slot-scope="{row}">
{{formatDate(row.votingStartTime)}} 至 {{formatDate(row.votingEndTime)}}
</template>
</el-table-column>
<el-table-column prop="voteCount" label="已投人数" width="90" align="center"></el-table-column>
<el-table-column label="操作" width="160" fixed="right" align="center">
<template slot-scope="{row}">
<el-button type="text" :disabled="row.voteCount > 0" @click="openEdit(row.id)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" class="text-danger" :disabled="row.voteCount > 0"
@click="doDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-dialog :title="formData.id ? '编辑投票' : '新增投票'" :visible.sync="dialogFormVisible"
width="80%" :close-on-click-modal="false">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="届次" prop="sessionId">
<el-select v-model="formData.sessionId" filterable style="width:100%" @change="sessionChanged">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="投票类型" prop="typeId">
<el-select v-model="formData.typeId" filterable style="width:100%" @change="typeChanged">
<el-option v-for="item in voteTypeOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="会议名称" prop="name">
<el-input v-model.trim="formData.name" maxlength="255" show-word-limit></el-input>
</el-form-item>
<el-form-item label="资料" prop="materialsId">
<div style="display:flex; gap:12px; width:100%; align-items:flex-start;">
<el-select v-model="formData.materialsId" filterable style="flex:1;"
:disabled="materialUploadDisabled">
<el-option v-for="item in materialOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
<!--<el-upload action="/platform/sys/file/uploadDynamicReturnUrl" :show-file-list="false"
style="width:180px; flex:0 0 180px;"
:disabled="materialUploadDisabled || materialUploadLoading"
:before-upload="beforeMaterialUpload"
:on-success="uploadMaterialSuccess"
:on-error="uploadMaterialError"
accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx">
<el-button type="primary" icon="el-icon-upload" style="width:180px"
:loading="materialUploadLoading"
:disabled="materialUploadDisabled || materialUploadLoading">上传资料</el-button>
</el-upload>-->
</div>
</el-form-item>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="投票开始" prop="votingStartTime">
<el-date-picker v-model="formData.votingStartTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" style="width:100%"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="投票结束" prop="votingEndTime">
<el-date-picker v-model="formData.votingEndTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" style="width:100%"></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="事项表头">
<el-input v-model.trim="formData.votingContentItemHeader" placeholder="表决事项"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="选项表头">
<el-input v-model.trim="formData.votingContentOptionHeader" placeholder="表决内容"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="投票内容" required>
<el-table :data="voteItems" border size="small">
<el-table-column label="表决事项" min-width="230">
<template slot-scope="{row}">
<el-input v-model.trim="row.itemName" maxlength="255" placeholder="请输入表决事项"></el-input>
</template>
</el-table-column>
<el-table-column label="表决选项(逗号分隔)" min-width="330">
<template slot-scope="{row}">
<el-input v-model.trim="row.optionsText" placeholder="赞成,不赞成,弃权"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template slot-scope="scope">
<el-button type="text" class="text-danger" @click="removeVoteItem(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-button class="mt10" size="small" icon="el-icon-plus" @click="addVoteItem">添加表决事项</el-button>
</el-form-item>
<el-form-item label="投票说明">
<el-input v-model.trim="formData.description" type="textarea" :rows="3"
maxlength="1000" show-word-limit></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="dialogFormVisible=false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="doSave">确定</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const validateEnd = (rule, value, callback) => {
if (!value) return callback(new Error("请选择投票结束时间"))
if (this.formData.votingStartTime && value <= this.formData.votingStartTime) {
return callback(new Error("投票结束时间必须晚于开始时间"))
}
callback()
}
return {
pageDataUrl: "/platform/congress/materials/issue/pageData",
sessionOptions: [],
typeOptions: [],
materialOptions: [],
materialUploadLoading: false,
materialLoadSeq: 0,
voteItems: [],
pageForm: {pageNumber: 1, pageSize: 10, totalCount: 0, sessionId: "", name: ""},
formData: {},
formRules: {
sessionId: [{required: true, message: "请选择届次", trigger: "change"}],
typeId: [{required: true, message: "请选择投票类型", trigger: "change"}],
name: [{required: true, message: "请输入会议名称", trigger: "blur"}],
materialsId: [{required: true, message: "请选择资料", trigger: "change"}],
votingStartTime: [{required: true, message: "请选择投票开始时间", trigger: "change"}],
votingEndTime: [{required: true, validator: validateEnd, trigger: "change"}]
}
}
},
computed: {
voteTypeOptions() {
return this.typeOptions.filter(v => Number(v.isVote) === 1)
},
materialUploadDisabled() {
return !this.formData.sessionId || !this.formData.typeId
}
},
methods: {
formatDate(value) {
return value ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : "-"
},
async loadOptions() {
const [sessionsRes, typesRes] = await Promise.all([
this.$axios.post("/platform/congress/materials/issue/sessions"),
this.$axios.post("/platform/congress/materials/issue/types")
])
if (sessionsRes.code === 0) this.sessionOptions = sessionsRes.data || []
if (typesRes.code === 0) this.typeOptions = typesRes.data || []
},
async loadMaterials() {
const seq = ++this.materialLoadSeq
this.materialOptions = []
if (!this.formData.sessionId || !this.formData.typeId) return
const res = await this.$axios.post("/platform/congress/materials/issue/materials", {
sessionId: this.formData.sessionId,
typeId: this.formData.typeId
})
if (seq === this.materialLoadSeq && res.code === 0) this.materialOptions = res.data || []
},
getUploadedUrl(res) {
if (!res) return ""
if (typeof res === "string") return res
return res.data || res.url || res.fileUrl || res.path || res.filePath || ""
},
fileNameWithoutExt(name) {
return name ? String(name).replace(/\.[^/.]+$/, "") : ""
},
fileExtension(name) {
const fileName = String(name || "")
const index = fileName.lastIndexOf(".")
return index >= 0 ? fileName.substring(index + 1).toLowerCase() : ""
},
beforeMaterialUpload(file) {
if (this.materialUploadDisabled) {
this.$message.warning("请先选择届次和投票类型")
return false
}
this.materialUploadLoading = true
return true
},
async uploadMaterialSuccess(response, file) {
const fileUrl = this.getUploadedUrl(response)
if (!fileUrl) {
this.materialUploadLoading = false
this.$message.error(response && response.msg ? response.msg : "上传资料失败")
return
}
const fileName = file.name || "附件"
try {
const res = await this.$axios.post("/platform/congress/materials/issue/uploadMaterial", {
sessionId: this.formData.sessionId,
typeId: this.formData.typeId,
name: this.fileNameWithoutExt(fileName),
fileName: fileName,
fileUrl: fileUrl,
fileType: this.fileExtension(fileName),
fileSize: file.raw && file.raw.size ? file.raw.size : ""
})
if (res.code === 0) {
await this.loadMaterials()
this.formData.materialsId = res.data && res.data.id ? res.data.id : res.data
this.$message.success("上传资料成功")
} else {
this.$message.error(res.msg)
}
} finally {
this.materialUploadLoading = false
}
},
uploadMaterialError() {
this.materialUploadLoading = false
this.$message.error("上传资料失败")
},
async sessionChanged() {
this.formData.materialsId = ""
this.materialOptions = []
await this.loadMaterials()
},
async typeChanged() {
this.formData.materialsId = ""
this.materialOptions = []
await this.loadMaterials()
},
addVoteItem() {
this.voteItems.push({itemName: "", optionsText: "赞成,不赞成,弃权"})
},
removeVoteItem(index) {
this.voteItems.splice(index, 1)
},
openAdd() {
this.formData = {
name: "", sessionId: "", typeId: "", materialsId: "",
votingStartTime: "", votingEndTime: "", description: "",
votingContentItemHeader: "表决事项", votingContentOptionHeader: "表决内容"
}
this.voteItems = [{itemName: "", optionsText: "赞成,不赞成,弃权"}]
this.materialOptions = []
this.dialogFormVisible = true
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
},
async openEdit(id) {
const res = await this.$axios.post("/platform/congress/materials/issue/get", {id})
if (res.code !== 0) return
const data = res.data || {}
data.votingStartTime = this.formatDate(data.votingStartTime)
data.votingEndTime = this.formatDate(data.votingEndTime)
this.formData = data
try {
this.voteItems = JSON.parse(data.votingContent || "[]").map(item => ({
itemName: item.itemName || item.item || "",
optionsText: (item.options || []).join(",")
}))
} catch (e) {
this.voteItems = []
}
await this.loadMaterials()
this.dialogFormVisible = true
},
buildVotingContent() {
const items = this.voteItems.map(item => ({
itemName: (item.itemName || "").trim(),
options: (item.optionsText || "").split(/[,]/)
.map(v => v.trim()).filter(Boolean)
}))
if (!items.length || items.some(item => !item.itemName || !item.options.length)) {
this.$message.warning("请完整填写每个表决事项及表决选项")
return null
}
return JSON.stringify(items)
},
doSave() {
this.$refs.formRef.validate(async valid => {
if (!valid) return
const votingContent = this.buildVotingContent()
if (!votingContent) return
this.submitLoading = true
try {
const data = Object.assign({}, this.formData, {votingContent})
const res = await this.$axios.post("/platform/congress/materials/issue/save", data)
if (res.code === 0) {
this.dialogFormVisible = false
this.$message.success("保存成功")
this.pageData()
} else {
this.$message.error(res.msg)
}
} finally {
this.submitLoading = false
}
})
},
doDelete(id) {
this.$confirm("确认删除该投票议题吗?", "提示", {type: "warning"}).then(async () => {
const res = await this.$axios.post("/platform/congress/materials/issue/delete", {id})
if (res.code === 0) {
this.$message.success("删除成功")
this.doSearch()
} else {
this.$message.error(res.msg)
}
})
}
},
async created() {
await this.loadOptions()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,236 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="届次">
<el-select v-model="pageForm.sessionId" clearable filterable placeholder="请选择届次">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="资料类型">
<el-select v-model="pageForm.typeId" clearable filterable placeholder="请选择资料类型">
<el-option v-for="item in typeOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="资料名称">
<el-input v-model.trim="pageForm.name" clearable placeholder="请输入资料名称"></el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="资料管理">
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAdd">新增</el-button>
</table-tool>
<el-table :data="tableData" v-loading="tableLoading" class="vi-table">
<el-table-column type="index" :index="indexMethod" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="name" label="资料名称" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="typeName" label="资料类型" min-width="140"></el-table-column>
<el-table-column label="届次" min-width="220">
<template slot-scope="{row}">{{row.sessionName}}</template>
</el-table-column>
<el-table-column prop="sortNo" label="排序号" width="90" align="center"></el-table-column>
<el-table-column prop="fileName" label="文件" min-width="180" show-overflow-tooltip>
<template slot-scope="{row}">
<el-link v-if="row.fileUrl" type="primary" :href="row.fileUrl" target="_blank">
{{row.fileName || row.name}}
</el-link>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="160" fixed="right" align="center">
<template slot-scope="{row}">
<el-button type="text" @click="openEdit(row.id)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" class="text-danger" @click="doDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-dialog :title="formData.id ? '编辑资料' : '新增资料'" :visible.sync="dialogFormVisible"
width="65%" :close-on-click-modal="false">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="届次" prop="sessionId">
<el-select v-model="formData.sessionId" filterable style="width:100%">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="资料类型" prop="typeId">
<el-select v-model="formData.typeId" filterable style="width:100%">
<el-option v-for="item in typeOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="资料名称" prop="name">
<el-input v-model.trim="formData.name" maxlength="255" show-word-limit
placeholder="不需要填写,默认是上传文件名称"></el-input>
</el-form-item>
<el-form-item label="排序号" prop="sortNo">
<el-input-number v-model="formData.sortNo" :min="0" :max="9999" style="width:100%"></el-input-number>
</el-form-item>
<el-form-item label="上传文件" prop="files">
<file-upload :value.sync="formData.files" :upload_number="1"
upload_result_type="url" upload_result_category="array"
complete_result accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"></file-upload>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="dialogFormVisible=false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="doSave">确定</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
pageDataUrl: "/platform/congress/materials/manage/pageData",
sessionOptions: [],
typeOptions: [],
pageForm: {
pageNumber: 1, pageSize: 10, totalCount: 0,
sessionId: "", typeId: "", name: ""
},
formData: {},
formRules: {
sessionId: [{required: true, message: "请选择届次", trigger: "change"}],
typeId: [{required: true, message: "请选择资料类型", trigger: "change"}],
name: [{required: true, message: "请输入资料名称", trigger: "blur"}],
files: [{required: true, message: "请上传文件", trigger: "change"}]
}
}
},
methods: {
async loadOptions() {
const [sessionsRes, typesRes] = await Promise.all([
this.$axios.post("/platform/congress/materials/manage/sessions"),
this.$axios.post("/platform/congress/materials/manage/types")
])
if (sessionsRes.code === 0) this.sessionOptions = sessionsRes.data || []
if (typesRes.code === 0) this.typeOptions = typesRes.data || []
},
openAdd() {
this.formData = {
name: "", sessionId: "", typeId: "",
sortNo: 0, fileName: "", fileUrl: "", fileType: "", fileSize: "", files: []
}
this.dialogFormVisible = true
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
},
async openEdit(id) {
const res = await this.$axios.post("/platform/congress/materials/manage/get", {id})
if (res.code === 0) {
const data = res.data || {}
this.formData = Object.assign({}, data, {
files: this.buildUploadFiles(data)
})
this.dialogFormVisible = true
}
},
buildUploadFiles(data) {
if (!data || !data.fileUrl) return []
return [{
name: data.fileName || this.fileNameFromUrl(data.fileUrl) || data.name || "附件",
url: data.fileUrl,
status: "success",
response: {
code: 0,
data: data.fileUrl
}
}]
},
fileNameFromUrl(url) {
if (!url) return ""
const path = String(url).split("?")[0]
const name = path.substring(path.lastIndexOf("/") + 1)
try {
return decodeURIComponent(name)
} catch (e) {
return name
}
},
fileNameWithoutExt(name) {
return name ? String(name).replace(/\.[^/.]+$/, "") : ""
},
fileExtension(name) {
const fileName = String(name || "")
const index = fileName.lastIndexOf(".")
return index >= 0 ? fileName.substring(index + 1).toLowerCase() : ""
},
getUploadFileUrl(file) {
return file && file.response && file.response.data ? file.response.data : file && (file.url || file.data) || ""
},
syncUploadFiles(files) {
const file = Array.isArray(files) && files.length > 0 ? files[files.length - 1] : null
if (!file) {
this.$set(this.formData, "fileName", "")
this.$set(this.formData, "fileUrl", "")
this.$set(this.formData, "fileType", "")
this.$set(this.formData, "fileSize", "")
return
}
const fileName = file.name || this.fileNameFromUrl(this.getUploadFileUrl(file))
this.$set(this.formData, "fileName", fileName)
this.$set(this.formData, "fileUrl", this.getUploadFileUrl(file))
this.$set(this.formData, "fileType", this.fileExtension(fileName))
this.$set(this.formData, "fileSize", file.raw && file.raw.size ? file.raw.size : file.size || "")
if (!this.formData.name && fileName) {
this.$set(this.formData, "name", this.fileNameWithoutExt(fileName))
}
},
doSave() {
this.syncUploadFiles(this.formData.files)
this.$refs.formRef.validate(async valid => {
if (!valid) return
this.submitLoading = true
try {
const res = await this.$axios.post("/platform/congress/materials/manage/save", this.formData)
if (res.code === 0) {
this.dialogFormVisible = false
this.$message.success("保存成功")
this.pageData()
} else {
this.$message.error(res.msg)
}
} finally {
this.submitLoading = false
}
})
},
doDelete(id) {
this.$confirm("确认删除该会议资料吗?", "提示", {type: "warning"}).then(async () => {
const res = await this.$axios.post("/platform/congress/materials/manage/delete", {id})
if (res.code === 0) {
this.$message.success("删除成功")
this.doSearch()
} else {
this.$message.error(res.msg)
}
})
}
},
watch: {
"formData.files": {
handler(files) {
this.syncUploadFiles(files)
},
deep: true
}
},
async created() {
await this.loadOptions()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,170 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="届次">
<el-select v-model="pageForm.sessionId" clearable filterable placeholder="请选择届次">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="会议名称">
<el-input v-model.trim="pageForm.name" clearable placeholder="请输入会议名称"></el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="投票统计"></table-tool>
<el-table :data="tableData" v-loading="tableLoading" class="vi-table">
<el-table-column type="index" :index="indexMethod" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="name" label="会议名称" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="typeName" label="投票类型" min-width="130"></el-table-column>
<el-table-column prop="materialsName" label="资料" min-width="180" show-overflow-tooltip>
<template slot-scope="{row}">
<el-link v-if="row.fileUrl" type="primary" :href="row.fileUrl" target="_blank">
{{row.materialsName}}
</el-link>
<span v-else>{{row.materialsName || "-"}}</span>
</template>
</el-table-column>
<el-table-column prop="voteCount" label="投票人数" width="100" align="center"></el-table-column>
<el-table-column label="届次" min-width="220">
<template slot-scope="{row}">{{row.sessionName}}</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center">
<template slot-scope="{row}">
<el-button type="text" @click="openDetail(row)">查看统计</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-drawer title="投票明细" :visible.sync="detailVisible" size="72%" :with-header="true">
<div style="padding:0 24px 24px">
<el-descriptions v-if="currentIssue.id" :column="2" border>
<el-descriptions-item label="会议名称">{{currentIssue.name}}</el-descriptions-item>
<el-descriptions-item label="届次">{{currentIssue.sessionName}}</el-descriptions-item>
<el-descriptions-item label="投票类型">{{currentIssue.typeName}}</el-descriptions-item>
<el-descriptions-item label="投票人数">{{currentIssue.voteCount}}</el-descriptions-item>
</el-descriptions>
<el-tabs v-model="detailTab" class="mt10">
<el-tab-pane label="投票统计" name="summary">
<el-table :data="summaryData" border v-loading="detailLoading">
<el-table-column type="index" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="itemName" label="投票项目/人员" min-width="200"></el-table-column>
<el-table-column prop="option" label="投票选项" min-width="160"></el-table-column>
<el-table-column prop="count" label="票数" width="100" align="center"></el-table-column>
<el-table-column label="占比" width="120" align="center">
<template slot-scope="{row}">{{percent(row.count, row.itemTotal)}}</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="投票记录" name="records">
<el-table :data="recordData" border v-loading="detailLoading">
<el-table-column type="index" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="emplid" label="工号" min-width="110"></el-table-column>
<el-table-column prop="name" label="代表" min-width="100"></el-table-column>
<el-table-column prop="unionName" label="工会" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="delegationName" label="代表团" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column label="投票结果" min-width="260" show-overflow-tooltip>
<template slot-scope="{row}">{{voteResultText(row.voteResult)}}</template>
</el-table-column>
<el-table-column label="投票时间" min-width="170">
<template slot-scope="{row}">{{formatDate(row.createTime)}}</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</el-drawer>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
pageDataUrl: "/platform/congress/materials/statistical/pageData",
sessionOptions: [],
detailVisible: false,
detailLoading: false,
detailTab: "summary",
currentIssue: {},
recordData: [],
summaryData: [],
pageForm: {pageNumber: 1, pageSize: 10, totalCount: 0, sessionId: "", name: ""}
}
},
methods: {
formatDate(value) {
return value ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : "-"
},
percent(count, total) {
return total ? (count * 100 / total).toFixed(2) + "%" : "0.00%"
},
parseVoteResult(value) {
if (!value) return []
try {
const data = typeof value === "string" ? JSON.parse(value) : value
return Array.isArray(data) ? data : []
} catch (e) {
return []
}
},
voteResultText(value) {
return this.parseVoteResult(value).map(item => {
return (item.item || item.itemName || "投票项") + "" +
(item.selected || item.option || item.value || "-")
}).join("")
},
buildSummary(records) {
const counter = {}
records.forEach(record => {
this.parseVoteResult(record.voteResult).forEach(item => {
const itemName = item.item || item.itemName || "投票项"
const option = item.selected || item.option || item.value || "-"
if (!counter[itemName]) counter[itemName] = {}
counter[itemName][option] = (counter[itemName][option] || 0) + 1
})
})
const rows = []
Object.keys(counter).forEach(itemName => {
const itemTotal = Object.values(counter[itemName]).reduce((sum, count) => sum + count, 0)
Object.keys(counter[itemName]).forEach(option => {
rows.push({itemName, option, count: counter[itemName][option], itemTotal})
})
})
return rows
},
async loadOptions() {
const res = await this.$axios.post("/platform/congress/materials/statistical/sessions")
if (res.code === 0) this.sessionOptions = res.data || []
},
async openDetail(row) {
this.currentIssue = row
this.detailVisible = true
this.detailLoading = true
this.detailTab = "summary"
try {
const res = await this.$axios.post("/platform/congress/materials/statistical/records", {issueId: row.id})
if (res.code === 0) {
this.recordData = res.data || []
this.summaryData = this.buildSummary(this.recordData)
}
} finally {
this.detailLoading = false
}
}
},
async created() {
await this.loadOptions()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,130 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="资料类型">
<el-input v-model.trim="pageForm.name" clearable placeholder="请输入资料类型"></el-input>
</search-item>
<search-item label="是否投票">
<el-select v-model="pageForm.isVote" clearable placeholder="请选择">
<el-option label="是" value="1"></el-option>
<el-option label="否" value="0"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="资料类型">
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAdd">新增</el-button>
</table-tool>
<el-table :data="tableData" v-loading="tableLoading" class="vi-table">
<el-table-column type="index" :index="indexMethod" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="name" label="资料类型" min-width="220"></el-table-column>
<el-table-column label="是否投票" width="120" align="center">
<template slot-scope="{row}">
<el-tag :type="row.isVote ? 'success' : 'info'" size="small">{{row.isVote ? "是" : "否"}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="sortNo" label="排序号" width="120" align="center"></el-table-column>
<el-table-column label="操作" width="160" align="center">
<template slot-scope="{row}">
<el-button type="text" @click="openEdit(row.id)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" class="text-danger" @click="doDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-dialog :title="formData.id ? '编辑资料类型' : '新增资料类型'"
:visible.sync="dialogFormVisible" width="50%" :close-on-click-modal="false">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="资料类型" prop="name">
<el-input v-model.trim="formData.name" maxlength="100" show-word-limit></el-input>
</el-form-item>
<el-form-item label="是否投票" prop="isVote">
<el-radio-group v-model="formData.isVote">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序号" prop="sortNo">
<el-input-number v-model="formData.sortNo" :min="0" :max="9999" style="width:100%"></el-input-number>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="dialogFormVisible=false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="doSave">确定</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
pageDataUrl: "/platform/congress/materials/type/pageData",
pageForm: {pageNumber: 1, pageSize: 10, totalCount: 0, name: "", isVote: ""},
formData: {},
formRules: {
name: [{required: true, message: "请输入资料类型", trigger: "blur"}],
sortNo: [{required: true, message: "请输入排序号", trigger: "change"}]
}
}
},
methods: {
openAdd() {
this.formData = {name: "", isVote: false, sortNo: 0}
this.dialogFormVisible = true
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
},
async openEdit(id) {
const res = await this.$axios.post("/platform/congress/materials/type/get", {id})
if (res.code === 0) {
this.formData = res.data || {}
this.formData.isVote = !!this.formData.isVote
this.dialogFormVisible = true
}
},
doSave() {
this.$refs.formRef.validate(async valid => {
if (!valid) return
this.submitLoading = true
try {
const res = await this.$axios.post("/platform/congress/materials/type/save", this.formData)
if (res.code === 0) {
this.dialogFormVisible = false
this.$message.success("保存成功")
this.pageData()
} else {
this.$message.error(res.msg)
}
} finally {
this.submitLoading = false
}
})
},
doDelete(id) {
this.$confirm("确认删除该资料类型吗?", "提示", {type: "warning"}).then(async () => {
const res = await this.$axios.post("/platform/congress/materials/type/delete", {id})
if (res.code === 0) {
this.$message.success("删除成功")
this.doSearch()
} else {
this.$message.error(res.msg)
}
})
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,84 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="届次">
<el-select v-model="pageForm.sessionId" clearable filterable placeholder="请选择届次" @change="loadMeetings">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="会议名称">
<el-select v-model="pageForm.meetingId" clearable filterable :disabled="!pageForm.sessionId">
<el-option v-for="item in meetingOptions" :key="item.id"
:label="item.meetingName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="关键字">
<el-input v-model.trim="pageForm.keyword" clearable placeholder="请输入工号或姓名"></el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="签到记录"></table-tool>
<el-table :data="tableData" v-loading="tableLoading" class="vi-table">
<el-table-column type="index" :index="indexMethod" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="emplid" label="工号" min-width="120"></el-table-column>
<el-table-column prop="name" label="姓名" min-width="100"></el-table-column>
<el-table-column prop="meetingName" label="会议名称" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column label="届次" min-width="220">
<template slot-scope="{row}">{{row.sessionName}}</template>
</el-table-column>
<el-table-column prop="delegationName" label="代表团" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column label="签到状态" width="100" align="center">
<template><el-tag type="success" size="small">已签到</el-tag></template>
</el-table-column>
<el-table-column label="签到时间" min-width="170">
<template slot-scope="{row}">{{formatDate(row.signInTime)}}</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
pageDataUrl: "/platform/congress/record/pageData",
sessionOptions: [],
meetingOptions: [],
pageForm: {
pageNumber: 1, pageSize: 10, totalCount: 0,
sessionId: "", meetingId: "", keyword: ""
}
}
},
methods: {
formatDate(value) {
return value ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : "-"
},
async loadOptions() {
const res = await this.$axios.post("/platform/congress/record/sessions")
if (res.code === 0) this.sessionOptions = res.data || []
},
async loadMeetings() {
this.pageForm.meetingId = ""
this.meetingOptions = []
if (!this.pageForm.sessionId) return
const res = await this.$axios.post("/platform/congress/record/meetings", {sessionId: this.pageForm.sessionId})
if (res.code === 0) this.meetingOptions = res.data || []
}
},
async created() {
await this.loadOptions()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,162 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="届次">
<el-select v-model="pageForm.sessionId" clearable filterable placeholder="请选择届次">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="会议管理">
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAdd">新增</el-button>
</table-tool>
<el-table :data="tableData" v-loading="tableLoading" class="vi-table">
<el-table-column type="index" :index="indexMethod" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="meetingName" label="会议名称" min-width="220" show-overflow-tooltip></el-table-column>
<el-table-column label="届次" min-width="220">
<template slot-scope="{row}">{{row.sessionName}}</template>
</el-table-column>
<el-table-column label="签到时间" min-width="300">
<template slot-scope="{row}">
{{formatDate(row.signInStartTime)}} 至 {{formatDate(row.signInEndTime)}}
</template>
</el-table-column>
<el-table-column prop="signInCount" label="签到人数" width="100" align="center"></el-table-column>
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="160" fixed="right" align="center">
<template slot-scope="{row}">
<el-button type="text" @click="openEdit(row.id)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" class="text-danger" @click="doDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-dialog :title="formData.id ? '编辑会议' : '新增会议'" :visible.sync="dialogFormVisible"
width="65%" :close-on-click-modal="false">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="届次" prop="sessionId">
<el-select v-model="formData.sessionId" filterable style="width:100%">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="会议名称" prop="meetingName">
<el-input v-model.trim="formData.meetingName" maxlength="255" show-word-limit></el-input>
</el-form-item>
<el-form-item label="签到开始" prop="signInStartTime">
<el-date-picker v-model="formData.signInStartTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" style="width:100%"></el-date-picker>
</el-form-item>
<el-form-item label="签到结束" prop="signInEndTime">
<el-date-picker v-model="formData.signInEndTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" style="width:100%"></el-date-picker>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model.trim="formData.remark" type="textarea" :rows="3"
maxlength="1000" show-word-limit></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="dialogFormVisible=false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="doSave">确定</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const validateEnd = (rule, value, callback) => {
if (!value) return callback(new Error("请选择签到结束时间"))
if (this.formData.signInStartTime && value <= this.formData.signInStartTime) {
return callback(new Error("签到结束时间必须晚于开始时间"))
}
callback()
}
return {
pageDataUrl: "/platform/congress/signIn/pageData",
sessionOptions: [],
pageForm: {pageNumber: 1, pageSize: 10, totalCount: 0, sessionId: ""},
formData: {},
formRules: {
sessionId: [{required: true, message: "请选择届次", trigger: "change"}],
meetingName: [{required: true, message: "请输入会议名称", trigger: "blur"}],
signInStartTime: [{required: true, message: "请选择签到开始时间", trigger: "change"}],
signInEndTime: [{required: true, validator: validateEnd, trigger: "change"}]
}
}
},
methods: {
formatDate(value) {
return value ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : "-"
},
async loadOptions() {
const res = await this.$axios.post("/platform/congress/signIn/sessions")
if (res.code === 0) this.sessionOptions = res.data || []
},
openAdd() {
this.formData = {meetingName: "", sessionId: "", remark: ""}
this.dialogFormVisible = true
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
},
async openEdit(id) {
const res = await this.$axios.post("/platform/congress/signIn/get", {id})
if (res.code === 0) {
const data = res.data || {}
data.signInStartTime = this.formatDate(data.signInStartTime)
data.signInEndTime = this.formatDate(data.signInEndTime)
this.formData = data
this.dialogFormVisible = true
}
},
doSave() {
this.$refs.formRef.validate(async valid => {
if (!valid) return
this.submitLoading = true
try {
const res = await this.$axios.post("/platform/congress/signIn/save", this.formData)
if (res.code === 0) {
this.dialogFormVisible = false
this.$message.success("保存成功")
this.pageData()
} else {
this.$message.error(res.msg)
}
} finally {
this.submitLoading = false
}
})
},
doDelete(id) {
this.$confirm("确认删除该会议签到配置吗?", "提示", {type: "warning"}).then(async () => {
const res = await this.$axios.post("/platform/congress/signIn/delete", {id})
if (res.code === 0) {
this.$message.success("删除成功")
this.doSearch()
} else {
this.$message.error(res.msg)
}
})
}
},
async created() {
await this.loadOptions()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,460 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="职代会" left-text="返回" left-arrow @click-left="returnH5Home('apps')" placeholder fixed></van-nav-bar>
<div class="congress-page">
<div class="empty-box" v-if="!loading && timesList.length === 0">
<van-icon name="notes-o" class="empty-icon"></van-icon>
<p class="empty-text">暂无参与届次信息</p>
</div>
<template v-else>
<div class="page-header">
<div class="session-info-card">
<h2 class="session-title">{{ selectedSessionName }}</h2>
<button v-if="timesList.length > 1" type="button" class="header-switch-btn" @click="showTimePicker = true">
<span>切换届次</span>
<van-icon name="arrow-down" size="12"></van-icon>
</button>
</div>
</div>
<div class="module-list-container">
<div class="modules-wrapper">
<div
v-for="module in moduleList"
:key="module.id"
class="module-card"
:class="resolveTheme(module.name)"
@click="handleModuleClick(module)"
>
<div class="module-header">
<div class="icon-wrapper">
<van-icon :name="module.icon" size="36"></van-icon>
</div>
<div class="module-info">
<h4 class="module-title">{{ module.name }}</h4>
<p class="module-desc" v-if="module.desc">{{ module.desc }}</p>
</div>
<van-icon name="arrow" class="arrow-icon"></van-icon>
</div>
</div>
</div>
</div>
</template>
<van-popup v-model="showTimePicker" position="bottom" :z-index="3000">
<van-picker
show-toolbar
:columns="formattedTimeColumns"
@confirm="onTimeConfirm"
@cancel="showTimePicker = false"
></van-picker>
</van-popup>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
data() {
return {
loading: true,
timesList: [],
selectedTimeId: "",
selectedSessionId: "",
selectedTimeText: "",
selectedSessionName: "",
selectedRepId: "",
showTimePicker: false,
moduleList: []
}
},
computed: {
formattedTimeColumns() {
return this.timesList.map((item) => ({
text: item.name,
value: item.timeId || item.id || item.sessionId
}))
}
},
methods: {
async initPage() {
const loading = this.$toast.loading({message: "加载中...", forbidClick: true, duration: 0})
try {
await this.fetchRepTimes()
if (this.timesList.length > 0) {
const initialTimeId = this.initialTimeId()
this.selectTime(this.resolveInitialTime(initialTimeId), !!initialTimeId)
await this.fetchModules()
}
} finally {
this.loading = false
loading.close()
}
},
loginName() {
const user = (this.$store.state && this.$store.state.user) || JSON.parse(window.sessionStorage.getItem("user") || "{}")
return user.loginname || user.loginName || ""
},
async fetchRepTimes() {
const res = await this.$axios.get("/platform/h5/congress/repTimes", {params: {emplid: this.loginName()}})
if (res.code === 0) {
this.timesList = res.data || []
}
},
initialTimeId() {
const params = new URLSearchParams(window.location.search)
return params.get("timeId") || params.get("sessionId") || ""
},
resolveInitialTime(timeId) {
return this.timesList.find((item) => this.sameTime(item, timeId)) || this.timesList[0]
},
async fetchModules() {
const res = await this.$axios.get("/platform/h5/congress/materialsTypeList")
if (res.code !== 0) return
const modules = (res.data || []).map((item) => ({
id: item.id,
name: item.name,
icon: this.resolveIcon(item.name),
desc: this.resolveDesc(item.name)
}))
const hasSignin = modules.some((item) => item.name && item.name.indexOf("会议签到") > -1)
this.moduleList = hasSignin ? modules : [{
id: "signin",
name: "会议签到",
icon: "completed",
desc: "点击进行会议签到"
}].concat(modules)
},
selectTime(item, syncUrl) {
this.selectedTimeId = item.timeId || item.id || item.sessionId || ""
this.selectedSessionId = item.sessionId || this.selectedTimeId
this.selectedTimeText = item.name || ""
this.selectedSessionName = item.sessionName || ""
this.selectedRepId = item.repId || ""
window.localStorage.setItem("congressTimeId", this.selectedTimeId)
window.localStorage.setItem("congressSessionId", this.selectedSessionId)
window.localStorage.setItem("timeName", this.selectedTimeText)
window.localStorage.setItem("sessionName", this.selectedSessionName)
window.localStorage.setItem("repId", this.selectedRepId)
if (syncUrl) {
this.syncSelectedTimeToUrl()
}
},
sameTime(item, timeId) {
if (!item || !timeId) return false
return item.id === timeId || item.timeId === timeId || item.sessionId === timeId
},
syncSelectedTimeToUrl() {
if (!this.selectedTimeId) return
const params = new URLSearchParams(window.location.search)
params.set("timeId", this.selectedTimeId)
const nextUrl = window.location.pathname + "?" + params.toString()
const state = Object.assign({}, window.history.state || {})
state.url = nextUrl
window.history.replaceState(state, document.title, nextUrl)
},
onTimeConfirm(value) {
const selected = this.timesList.find((item) => this.sameTime(item, value.value))
if (selected) {
this.selectTime(selected, true)
this.fetchModules()
}
this.showTimePicker = false
},
handleModuleClick(module) {
if (!this.selectedTimeId) {
this.$toast("请先选择届次")
return
}
if (module.id === "signin" || module.name.indexOf("会议签到") > -1) {
this.$pjaxReplace("/platform/h5/congress/meeting/list?moduleId=signin&moduleName=会议签到&timeId=" + encodeURIComponent(this.selectedTimeId) +
"&sessionId=" + encodeURIComponent(this.selectedSessionId))
return
}
this.$pjaxReplace("/platform/h5/congress/materials/list?moduleId=" + encodeURIComponent(module.id) +
"&typeId=" + encodeURIComponent(module.id) +
"&moduleName=" + encodeURIComponent(module.name) +
"&timeId=" + encodeURIComponent(this.selectedTimeId) +
"&sessionId=" + encodeURIComponent(this.selectedSessionId))
},
resolveIcon(name) {
if (!name) return "orders-o"
if (name.indexOf("会议签到") > -1) return "completed"
if (name.indexOf("评议") > -1) return "star"
if (name.indexOf("议题") > -1) return "todo-list"
if (name.indexOf("资料") > -1) return "notes"
if (name.indexOf("公告") > -1 || name.indexOf("结果") > -1) return "bell"
if (name.indexOf("文件") > -1) return "description"
return "orders"
},
resolveDesc(name) {
if (!name) return ""
if (name.indexOf("会议签到") > -1) return "点击进行会议签到"
if (name.indexOf("文件") > -1) return "查看会议文件公告"
if (name.indexOf("评议") > -1) return "查看并参与评议表决"
if (name.indexOf("议题") > -1) return "查看并参与议题表决"
if (name.indexOf("表决公告") > -1) return "查看表决结果公告"
if (name.indexOf("资料") > -1) return "查看会议准备资料"
return ""
},
resolveTheme(name) {
if (!name) return "theme-blue"
if (name.indexOf("会议签到") > -1) return "theme-blue"
if (name.indexOf("文件") > -1) return "theme-blue"
if (name.indexOf("表决公告") > -1) return "theme-green"
if (name.indexOf("议题") > -1) return "theme-purple"
if (name.indexOf("评议") > -1) return "theme-orange module-card-wide"
return "theme-blue"
}
},
created() {
this.initPage()
}
})
</script>
<style id="style-congress-h5">
.congress-page {
height: calc(100vh - 46px);
background: #f4f8ff;
overflow: hidden;
display: flex;
flex-direction: column;
}
.page-header {
min-height: 192px;
background: #1686ff url("${base!}/images/congress/h5-congress-header.png") center center / cover no-repeat;
padding: 0px 15px 50px;
color: #fff;
box-shadow: 0 12px 28px rgba(25, 137, 250, 0.18);
display: flex;
justify-content: flex-start;
align-items: center;
position: relative;
overflow: hidden;
flex-shrink: 0;
}
.session-info-card {
display: flex;
flex-direction: column;
align-items: flex-start;
text-align: left;
position: relative;
z-index: 3;
max-width: 50%;
}
.session-title {
font-size: 25px;
font-weight: 700;
line-height: 1.25;
color: #fff;
margin: 0 0 10px;
text-shadow: 0 4px 12px rgba(3, 77, 168, 0.18);
}
.header-switch-btn {
margin-top: 6px;
height: 34px;
padding: 0 14px;
border: 0;
border-radius: 18px;
background: rgba(255, 255, 255, 0.18);
color: #fff;
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 600;
line-height: 1;
cursor: pointer;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.36), 0 8px 18px rgba(0, 66, 160, 0.12);
backdrop-filter: blur(8px);
}
.header-switch-btn:active {
background: rgba(255, 255, 255, 0.28);
transform: scale(0.98);
}
.module-list-container {
position: relative;
z-index: 2;
flex: 1;
min-height: 0;
margin: -72px 0 0;
padding: 18px 16px 24px;
background: #fff;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.modules-wrapper {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 12px;
}
.module-card {
min-height: 98px;
background: linear-gradient(135deg, #f8fbff 0%, #fff 100%);
border-radius: 14px;
box-shadow: 0 8px 18px rgba(27, 56, 105, 0.06);
overflow: hidden;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid rgba(237, 242, 250, 0.88);
position: relative;
}
.module-card-wide {
grid-column: auto;
}
.module-card:active {
transform: scale(0.99);
box-shadow: 0 4px 16px rgba(25, 137, 250, 0.08);
}
.module-header {
min-height: 60px;
padding: 14px;
display: flex;
align-items: center;
gap: 12px;
}
.icon-wrapper {
width: 54px;
height: 54px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
background: #eef5ff;
flex-shrink: 0;
box-shadow: 0 8px 18px rgba(25, 137, 250, 0.1);
color: #1989fa;
}
.module-info {
flex: 1;
min-width: 0;
}
.module-title {
font-size: 16px;
font-weight: 700;
color: #111a3a;
margin: 0 0 6px;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.module-desc {
font-size: 12px;
color: #7d89a6;
margin: 0;
line-height: 1.5;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.arrow-icon {
width: 26px;
height: 26px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #1989fa;
background: rgba(25, 137, 250, 0.1);
flex-shrink: 0;
transition: all 0.3s ease;
}
.theme-green {
background: linear-gradient(135deg, #f3fff9 0%, #fff 100%);
}
.theme-green .icon-wrapper {
color: #17c979;
background: #eafbf2;
box-shadow: 0 8px 18px rgba(23, 201, 121, 0.12);
}
.theme-green .arrow-icon {
color: #17b977;
background: rgba(23, 201, 121, 0.12);
}
.theme-purple {
background: linear-gradient(135deg, #fbf7ff 0%, #fff 100%);
}
.theme-purple .icon-wrapper {
color: #8756e9;
background: #f1eaff;
box-shadow: 0 8px 18px rgba(135, 86, 233, 0.12);
}
.theme-purple .arrow-icon {
color: #8756e9;
background: rgba(135, 86, 233, 0.12);
}
.theme-orange {
background: linear-gradient(135deg, #fff8ed 0%, #fff 100%);
}
.theme-orange .icon-wrapper {
color: #ff9f1a;
background: #fff2df;
box-shadow: 0 8px 18px rgba(255, 159, 26, 0.12);
}
.theme-orange .arrow-icon {
color: #d9a05b;
background: rgba(255, 159, 26, 0.12);
}
.empty-box {
min-height: calc(100vh - 46px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 24px;
box-sizing: border-box;
text-align: center;
}
.empty-icon {
font-size: 64px;
color: #d9d9d9;
margin-bottom: 16px;
}
.empty-text {
font-size: 14px;
color: #999;
margin: 0;
}
</style>
<!--#
}
#-->
@@ -0,0 +1,432 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar :title="pageTitle" left-text="返回" left-arrow @click-left="backToCongress" placeholder fixed></van-nav-bar>
<div class="file-list-page">
<van-sticky :offset-top="46">
<div class="file-filter-bar">
<van-search v-model="keyword" placeholder="搜索文件名" shape="round" class="file-search" @input="onKeywordChange"></van-search>
<van-dropdown-menu class="file-filter-menu">
<van-dropdown-item v-model="selectedFileType" :options="fileTypeOptions" @change="resetList"></van-dropdown-item>
<van-dropdown-item v-model="uploadTimeOrder" :options="uploadTimeSortOptions" @change="resetList"></van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list v-model="loading" :finished="finished" :finished-text="fileList.length > 0 ? '没有更多了' : ''" @load="onLoad">
<div v-for="item in fileList" :key="item.id" class="file-card" @click="handleFileClick(item)">
<div class="card-left">
<div class="icon-wrapper">
<img :src="getFileIconUrl(item)" :alt="resolveFileSuffix(item) || 'file'" class="file-type-icon">
</div>
</div>
<div class="card-content">
<div class="card-header">
<span class="file-name">{{ formatFileName(item.name || item.fileName) }}</span>
<div class="card-actions">
<span v-if="item.hasVoted !== null && item.hasVoted !== undefined" class="vote-status" :class="isVoted(item.hasVoted) ? 'voted' : 'pending'">
<van-icon :name="isVoted(item.hasVoted) ? 'checked' : 'clock-o'" size="12"></van-icon>
{{ isVoted(item.hasVoted) ? '已投票' : '待投票' }}
</span>
<van-icon name="arrow" class="arrow-icon"></van-icon>
</div>
</div>
<div class="file-info">
<div class="info-row">
<van-icon name="description" size="14"></van-icon>
<span class="info-text">文件类型:<span class="info-value">{{ fileTypeText(item) }}</span></span>
</div>
<div v-if="item.uploadTime" class="info-row">
<van-icon name="clock-o" size="14"></van-icon>
<span class="info-text">上传时间:<span class="info-value">{{ formatTime(item.uploadTime) }}</span></span>
</div>
</div>
</div>
</div>
<van-empty v-if="!loading && fileList.length === 0" :description="hasFilter ? '未找到匹配文件' : '暂无文件'"></van-empty>
</van-list>
</van-pull-refresh>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
data() {
return {
pageTitle: "文件公告",
fileList: [],
loading: false,
finished: false,
refreshing: false,
loadedOnce: false,
typeId: "",
timeId: "",
sessionId: "",
keyword: "",
selectedFileType: "",
uploadTimeOrder: "desc",
searchTimer: null,
queryVersion: 0,
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0
}
}
},
computed: {
fileTypeOptions() {
return [
{text: "全部类型", value: ""},
{text: "PDF", value: "pdf"},
{text: "Word", value: "doc,docx"},
{text: "Excel", value: "xls,xlsx"},
{text: "PPT", value: "ppt,pptx"},
{text: "图片", value: "jpg,jpeg,png,gif,bmp,webp"},
{text: "压缩包", value: "zip,rar"}
]
},
uploadTimeSortOptions() {
return [
{text: "上传时间:最新", value: "desc"},
{text: "上传时间:最早", value: "asc"}
]
},
hasFilter() {
return !!(this.keyword || this.selectedFileType)
}
},
methods: {
initQuery() {
const params = new URLSearchParams(window.location.search)
this.pageTitle = params.get("moduleName") || "文件公告"
this.typeId = params.get("typeId") || params.get("moduleId") || ""
this.timeId = params.get("timeId") || ""
this.sessionId = params.get("sessionId") || this.timeId
},
backToCongress() {
const timeId = this.timeId || this.sessionId || window.localStorage.getItem("congressTimeId") || ""
this.$pjaxReplace("/platform/h5/congress" + (timeId ? "?timeId=" + encodeURIComponent(timeId) : ""))
},
loginName() {
const user = (this.$store.state && this.$store.state.user) || JSON.parse(window.sessionStorage.getItem("user") || "{}")
return user.loginname || user.loginName || ""
},
onRefresh() {
this.resetList()
},
onLoad() {
if (!this.finished) {
this.fetchFileList()
}
},
onKeywordChange() {
clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => this.resetList(), 300)
},
resetList() {
this.queryVersion++
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.fileList = []
this.finished = false
this.loadedOnce = false
this.fetchFileList()
},
fetchFileList() {
const queryVersion = this.queryVersion
this.loading = true
this.$axios.get("/platform/h5/congress/materialsList", {
params: {
typeId: this.typeId,
timeId: this.timeId,
emplid: this.loginName(),
searchKeyword: this.keyword,
fileType: this.selectedFileType,
uploadTimeOrder: this.uploadTimeOrder,
pageNumber: this.pageForm.pageNumber,
pageSize: this.pageForm.pageSize
}
}).then((res) => {
if (queryVersion !== this.queryVersion) return
if (res.code === 0) {
const pageData = res.data || {}
const records = pageData.list || []
this.fileList = this.fileList.concat(records)
this.pageForm.totalCount = pageData.totalCount || 0
if (this.fileList.length >= this.pageForm.totalCount) {
this.finished = true
} else {
this.pageForm.pageNumber++
}
} else {
this.finished = true
}
this.loadedOnce = true
}).finally(() => {
if (queryVersion === this.queryVersion) {
this.loading = false
this.refreshing = false
}
})
},
getFileIconUrl(item) {
return this.$commonUtil.getFileItemShowIcon(this.resolveFileSuffix(item))
},
resolveFileSuffix(item) {
const fileType = String(item.fileType || "").replace(".", "").toLowerCase()
if (this.isKnownSuffix(fileType)) return fileType
const fileNameSuffix = this.extractSuffix(item.fileName || item.name || "")
if (fileNameSuffix) return fileNameSuffix
const fileUrlSuffix = this.extractSuffix((item.fileUrl || "").split("?")[0])
if (fileUrlSuffix) return fileUrlSuffix
if (fileType.indexOf("pdf") > -1) return "pdf"
if (fileType.indexOf("word") > -1) return "docx"
if (fileType.indexOf("excel") > -1 || fileType.indexOf("sheet") > -1) return "xlsx"
if (fileType.indexOf("powerpoint") > -1 || fileType.indexOf("presentation") > -1) return "pptx"
if (fileType.indexOf("image") > -1) return "jpg"
if (fileType.indexOf("zip") > -1 || fileType.indexOf("rar") > -1) return "zip"
if (fileType.indexOf("text") > -1) return "txt"
if (fileType.indexOf("video") > -1) return "mp4"
if (fileType.indexOf("audio") > -1) return "mp3"
return ""
},
extractSuffix(value) {
const text = String(value || "").trim()
const index = text.lastIndexOf(".")
if (index < 0 || index === text.length - 1) return ""
const suffix = text.substring(index + 1).toLowerCase()
return this.isKnownSuffix(suffix) ? suffix : ""
},
isKnownSuffix(suffix) {
return ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "zip", "rar", "jpg", "jpeg", "png", "gif", "bmp", "webp", "mp4", "avi", "wmv", "rmvb", "flv", "mkv", "mp3", "wav", "wma", "flac", "ape"].indexOf(suffix) > -1
},
fileTypeText(item) {
const suffix = this.resolveFileSuffix(item)
return suffix ? suffix.toUpperCase() : "未知类型"
},
formatTime(time) {
if (!time) return ""
const date = new Date(time)
if (Number.isNaN(date.getTime())) return time
const pad = (value) => String(value).padStart(2, "0")
return date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + " " + pad(date.getHours()) + ":" + pad(date.getMinutes())
},
formatFileName(fileName) {
return fileName ? String(fileName).trim().replace(/\s/g, "") : ""
},
handleFileClick(item) {
if (item.hasVoted !== null && item.hasVoted !== undefined) {
this.openVotePdf(item)
return
}
if (!item.fileUrl) {
this.$toast("暂无可预览附件")
return
}
this.$pjaxReplace("/platform/h5/congress/pdf?pdfPath=" + encodeURIComponent(item.fileUrl) +
"&fileName=" + encodeURIComponent(item.fileName || item.name || "") +
"&fileType=" + encodeURIComponent(item.fileType || "") +
"&sysFileId=" + encodeURIComponent(this.resolveSysFileId(item.fileUrl)))
},
openVotePdf(item) {
this.$pjaxReplace("/platform/h5/congress/pdf?pdfPath=" + encodeURIComponent(item.fileUrl || "") +
"&fileName=" + encodeURIComponent(item.fileName || item.name || "") +
"&materialsId=" + encodeURIComponent(item.id || "") +
"&fileType=" + encodeURIComponent(item.fileType || "") +
"&sysFileId=" + encodeURIComponent(this.resolveSysFileId(item.fileUrl)))
},
resolveSysFileId(fileUrl) {
if (!fileUrl) return ""
try {
const url = new URL(fileUrl, window.location.origin)
return url.searchParams.get("id") || ""
} catch (e) {
const match = String(fileUrl).match(/[?&]id=([^&]+)/)
return match ? decodeURIComponent(match[1]) : ""
}
},
isVoted(value) {
return value === true || value === 1 || value === "1" || value === "true"
}
},
created() {
this.initQuery()
}
})
</script>
<style id="style-congress-materials-h5">
.file-list-page {
min-height: calc(100vh - 46px);
background: #f5f7fa;
padding-top: 1px;
}
.file-filter-bar {
background: #fff;
box-shadow: 0 2px 8px rgba(30, 48, 80, 0.04);
}
.file-search {
padding: 10px 16px 6px;
background: transparent;
}
.file-filter-menu {
height: 42px;
box-shadow: none;
}
.file-filter-menu .van-dropdown-menu__bar {
height: 42px;
box-shadow: none;
border-top: 1px solid #f2f3f5;
}
.file-card {
background: #fff;
margin: 12px 16px;
padding: 18px 16px;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 16px;
position: relative;
overflow: hidden;
border: 1px solid rgba(0, 0, 0, 0.03);
}
.file-card::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: linear-gradient(180deg, #1989fa 0%, #0d6efd 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.file-card:active {
transform: scale(0.99);
box-shadow: 0 4px 16px rgba(25, 137, 250, 0.08);
}
.card-actions {
display: inline-flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.vote-status {
display: inline-flex;
align-items: center;
gap: 3px;
padding: 4px 7px;
font-size: 12px;
font-weight: 600;
line-height: 1;
border-radius: 10px;
}
.vote-status.voted {
color: #17a65b;
background: #edf9f1;
}
.vote-status.pending {
color: #d98a00;
background: #fff6e5;
}
.card-left {
flex-shrink: 0;
display: flex;
align-items: center;
}
.icon-wrapper {
width: 52px;
height: 52px;
border-radius: 12px;
background: #f7f8fa;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #eef0f4;
}
.file-type-icon {
width: 38px;
height: 38px;
display: block;
object-fit: contain;
}
.card-content {
flex: 1;
min-width: 0;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.file-name {
font-size: 15px;
font-weight: 600;
color: #4a4a4a;
line-height: 1.4;
word-break: break-word;
flex: 1;
margin-right: 8px;
}
.arrow-icon {
color: #c8c9cc;
font-size: 16px;
flex-shrink: 0;
}
.info-row {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: #969799;
line-height: 1.5;
}
.info-row + .info-row {
margin-top: 4px;
}
.info-row .van-icon {
color: #969799;
}
.info-value {
color: #646566;
font-weight: 500;
}
</style>
<!--#
}
#-->
@@ -0,0 +1,466 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="会议签到" left-text="返回" left-arrow @click-left="backToCongress" placeholder fixed></van-nav-bar>
<div class="meeting-list-page">
<van-sticky :offset-top="46">
<div class="meeting-filter-bar">
<van-search v-model="keyword" placeholder="搜索会议名称" shape="round" class="meeting-search" @input="onKeywordChange"></van-search>
<van-dropdown-menu class="meeting-filter-menu">
<van-dropdown-item v-model="signTimeStatus" :options="signTimeOptions" @change="resetList"></van-dropdown-item>
<van-dropdown-item v-model="signInCountStatus" :options="signInCountOptions" @change="resetList"></van-dropdown-item>
<van-dropdown-item v-model="signStatus" :options="signStatusOptions" @change="resetList"></van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list v-model="loading" :finished="finished" :finished-text="meetingList.length > 0 ? '没有更多了' : ''" @load="onLoad">
<div v-for="item in meetingList" :key="item.id" class="meeting-card" :class="{'meeting-card-disabled': !item.signInId && !canSignIn(item)}">
<div class="card-content">
<div class="card-header">
<div class="card-left">
<div class="icon-wrapper" :class="{'signed-icon': item.signInId}">
<van-icon name="completed" size="26"></van-icon>
</div>
</div>
<span class="meeting-name">{{ item.meetingName }}</span>
<button v-if="item.remark" type="button" class="remark-link" @click.stop="openRemark(item)">
<van-icon name="notes-o" size="14"></van-icon>
<span>备注</span>
<van-icon name="arrow" size="12"></van-icon>
</button>
</div>
<div class="info-list">
<div v-if="item.signInStartTime" class="info-row">
<van-icon name="clock-o" size="14" color="#969799"></van-icon>
<span class="info-text">签到开始:<span class="info-value">{{ formatTime(item.signInStartTime) }}</span></span>
</div>
<div v-if="item.signInEndTime" class="info-row">
<van-icon name="clock-o" size="14" color="#969799"></van-icon>
<span class="info-text">签到结束:<span class="info-value">{{ formatTime(item.signInEndTime) }}</span></span>
</div>
<div class="info-row count">
<van-icon name="friends-o" size="14" color="#1989fa"></van-icon>
<span class="info-text">签到人数:<span class="info-value">{{ item.signInCount || 0 }} 人</span></span>
</div>
</div>
<div v-if="item.userSignInTime" class="action-section signin-status signed-status">
<van-icon name="checked" size="16"></van-icon>
<span>已签到:{{ formatTime(item.userSignInTime) }}</span>
</div>
<div v-else class="action-section">
<div v-if="!canSignIn(item)" class="signin-status disabled-status">
<van-icon name="clock-o" size="16"></van-icon>
{{ getSignInButtonText(item) }}
</div>
<van-button v-else type="primary" class="signin-btn" @click.stop="handleSignInClick(item)">
<van-icon name="checked" size="16"></van-icon>
立即签到
</van-button>
</div>
</div>
</div>
<van-empty v-if="!loading && meetingList.length === 0" :description="hasFilter ? '未找到匹配会议' : '暂无会议'"></van-empty>
</van-list>
</van-pull-refresh>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
data() {
return {
meetingList: [],
loading: false,
finished: false,
refreshing: false,
loadedOnce: false,
timeId: "",
sessionId: "",
keyword: "",
signTimeStatus: "",
signInCountStatus: "",
signStatus: "",
searchTimer: null,
queryVersion: 0,
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0
}
}
},
computed: {
signTimeOptions() {
return [
{text: "签到时间", value: ""},
{text: "未开始", value: "upcoming"},
{text: "进行中", value: "ongoing"},
{text: "已结束", value: "ended"}
]
},
signInCountOptions() {
return [
{text: "签到人数", value: ""},
{text: "有人签到", value: "has"},
{text: "无人签到", value: "none"}
]
},
signStatusOptions() {
return [
{text: "签到状态", value: ""},
{text: "已签到", value: "signed"},
{text: "未签到", value: "unsigned"}
]
},
hasFilter() {
return !!(this.keyword || this.signTimeStatus || this.signInCountStatus || this.signStatus)
}
},
methods: {
initQuery() {
const params = new URLSearchParams(window.location.search)
this.timeId = params.get("timeId") || ""
this.sessionId = params.get("sessionId") || this.timeId
},
backToCongress() {
const timeId = this.timeId || this.sessionId || window.localStorage.getItem("congressTimeId") || ""
this.$pjaxReplace("/platform/h5/congress" + (timeId ? "?timeId=" + encodeURIComponent(timeId) : ""))
},
loginName() {
const user = (this.$store.state && this.$store.state.user) || JSON.parse(window.sessionStorage.getItem("user") || "{}")
return user.loginname || user.loginName || ""
},
onRefresh() {
this.resetList()
},
onLoad() {
if (!this.finished) {
this.fetchMeetingList()
}
},
onKeywordChange() {
clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => this.resetList(), 300)
},
resetList() {
this.queryVersion++
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.meetingList = []
this.finished = false
this.loadedOnce = false
this.fetchMeetingList()
},
fetchMeetingList() {
const queryVersion = this.queryVersion
this.loading = true
this.$axios.get("/platform/h5/congress/user/meetingList", {
params: {
timeId: this.timeId,
emplid: this.loginName(),
searchKeyword: this.keyword,
signTimeStatus: this.signTimeStatus,
signInCountStatus: this.signInCountStatus,
signStatus: this.signStatus,
pageNumber: this.pageForm.pageNumber,
pageSize: this.pageForm.pageSize
}
}).then((res) => {
if (queryVersion !== this.queryVersion) return
if (res.code === 0) {
const pageData = res.data || {}
const records = pageData.list || []
this.meetingList = this.meetingList.concat(records)
this.pageForm.totalCount = pageData.totalCount || 0
if (this.meetingList.length >= this.pageForm.totalCount) {
this.finished = true
} else {
this.pageForm.pageNumber++
}
} else {
this.finished = true
}
this.loadedOnce = true
}).finally(() => {
if (queryVersion === this.queryVersion) {
this.loading = false
this.refreshing = false
}
})
},
openRemark(item) {
this.$pjaxReplace("/platform/h5/congress/meeting/remark?meetingId=" + encodeURIComponent(item.id) +
"&timeId=" + encodeURIComponent(item.timeId || this.timeId) +
"&sessionId=" + encodeURIComponent(item.sessionId || this.sessionId))
},
handleSignInClick(item) {
if (item.signInId) {
this.$toast.success("您已签到,无需重复签到")
return
}
if (!this.canSignIn(item)) {
this.$toast(this.getSignInButtonText(item))
return
}
this.$dialog.confirm({
title: "签到确认",
message: "确认要对 \"" + item.meetingName + "\" 进行签到吗?",
confirmButtonText: "确认签到",
cancelButtonText: "取消",
confirmButtonColor: "#1989fa"
}).then(() => {
const body = new URLSearchParams()
body.append("repId", item.repId || window.localStorage.getItem("repId") || "")
body.append("sessionId", item.sessionId || "")
body.append("timeId", item.timeId || this.timeId)
body.append("meetingId", item.id)
return this.$axios.post("/platform/h5/congress/signin", body).then((res) => {
if (res.code === 0) {
this.$toast.success("签到成功")
this.$set(item, "signInId", Date.now())
this.$set(item, "userSignInTime", new Date())
this.$set(item, "signInCount", (item.signInCount || 0) + 1)
}
})
}).catch(() => {})
},
canSignIn(item) {
if (!item.signInStartTime || !item.signInEndTime) return true
const now = Date.now()
const start = new Date(item.signInStartTime).getTime()
const end = new Date(item.signInEndTime).getTime()
if (Number.isNaN(start) || Number.isNaN(end)) return true
return now >= start && now <= end
},
getSignInButtonText(item) {
if (!item.signInStartTime || !item.signInEndTime) return "立即签到"
const now = Date.now()
const start = new Date(item.signInStartTime).getTime()
const end = new Date(item.signInEndTime).getTime()
if (Number.isNaN(start) || Number.isNaN(end)) return "立即签到"
if (now < start) return "未到签到时间"
if (now > end) return "已过签到时间"
return "立即签到"
},
formatTimeRange(startTime, endTime) {
if (!startTime || !endTime) return ""
const start = this.formatTime(startTime)
const end = this.formatTime(endTime)
return start.slice(0, 10) === end.slice(0, 10) ? start + " ~ " + end.slice(11) : start + " ~ " + end
},
formatTime(time) {
if (!time) return ""
const date = new Date(time)
if (Number.isNaN(date.getTime())) return time
const pad = (value) => String(value).padStart(2, "0")
return date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + " " + pad(date.getHours()) + ":" + pad(date.getMinutes())
}
},
created() {
this.initQuery()
}
})
</script>
<style id="style-congress-meeting-h5">
.meeting-list-page {
min-height: calc(100vh - 46px);
background: #f5f7fa;
padding-top: 1px;
}
.meeting-filter-bar {
background: #fff;
box-shadow: 0 2px 8px rgba(30, 48, 80, 0.04);
}
.meeting-search {
padding: 10px 16px 6px;
background: transparent;
}
.meeting-filter-menu {
height: 42px;
box-shadow: none;
}
.meeting-filter-menu .van-dropdown-menu__bar {
height: 42px;
box-shadow: none;
border-top: 1px solid #f2f3f5;
}
.meeting-card {
background: #fff;
margin: 16px;
height: 220px;
padding: 24px;
box-sizing: border-box;
border-radius: 18px;
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: stretch;
position: relative;
overflow: hidden;
border: 1px solid rgba(0, 0, 0, 0.03);
}
.meeting-card::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 5px;
background: linear-gradient(180deg, #1989fa 0%, #0d6efd 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.card-left {
flex-shrink: 0;
padding-top: 0;
}
.icon-wrapper {
width: 44px;
height: 44px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #f0f5ff 0%, #e6f0ff 100%);
color: #1989fa;
box-shadow: 0 2px 8px rgba(25, 137, 250, 0.15);
}
.icon-wrapper.signed-icon {
background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
color: #fff;
}
.meeting-card-disabled .icon-wrapper {
background: #f4f4f5;
color: #c8c9cc;
box-shadow: none;
}
.card-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.card-header {
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 12px;
}
.meeting-name {
font-size: 17px;
color: #323233;
font-weight: 600;
line-height: 1.5;
flex: 1;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.remark-link {
display: inline-flex;
align-items: center;
gap: 2px;
margin-left: 8px;
padding: 0;
border: 0;
background: transparent;
color: #969799;
font-size: 13px;
line-height: 1.5;
flex-shrink: 0;
}
.info-list {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 0;
}
.info-row {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
line-height: 1.4;
}
.info-text {
flex: 1;
color: #646566;
word-break: break-word;
}
.info-row.count .info-text {
color: #1989fa;
}
.meeting-card-disabled .info-row.count .van-icon {
color: #c8c9cc !important;
}
.meeting-card-disabled .info-row.count .info-text {
color: #c8c9cc;
}
.action-section {
margin-top: auto;
}
.signin-status {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
line-height: 1.4;
}
.signed-status {
color: #07c160;
}
.disabled-status {
color: #c8c9cc;
}
.signin-btn {
width: 100%;
padding: 10px 16px;
border-radius: 12px;
font-size: 14px;
font-weight: 600;
background: linear-gradient(135deg, #1989fa 0%, #0d6efd 100%);
border: none;
box-shadow: 0 3px 10px rgba(25, 137, 250, 0.3);
transform: translateY(4px);
}
</style>
<!--#
}
#-->
@@ -0,0 +1,104 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="会议备注" left-text="返回" left-arrow @click-left="backToMeeting" placeholder fixed></van-nav-bar>
<div class="meeting-remark-page">
<div v-if="remarkData" class="remark-content-card">
<h2 class="remark-title">{{ remarkData.meetingName }}</h2>
<div class="remark-divider"></div>
<div class="remark-content">{{ remarkData.remark || "暂无备注" }}</div>
</div>
<van-empty v-else-if="!loading" description="暂无备注"></van-empty>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
data() {
return {
loading: true,
meetingId: "",
timeId: "",
sessionId: "",
remarkData: null
}
},
methods: {
initQuery() {
const params = new URLSearchParams(window.location.search)
this.meetingId = params.get("meetingId") || ""
this.timeId = params.get("timeId") || ""
this.sessionId = params.get("sessionId") || this.timeId
},
backToMeeting() {
this.$pjaxReplace("/platform/h5/congress/meeting/list?timeId=" + encodeURIComponent(this.timeId) +
"&sessionId=" + encodeURIComponent(this.sessionId))
},
loginName() {
const user = (this.$store.state && this.$store.state.user) || JSON.parse(window.sessionStorage.getItem("user") || "{}")
return user.loginname || user.loginName || ""
},
fetchRemark() {
this.$axios.get("/platform/h5/congress/user/meetingRemark", {
params: {meetingId: this.meetingId, timeId: this.timeId, emplid: this.loginName()}
}).then((res) => {
if (res.code === 0) {
this.remarkData = res.data || null
}
}).finally(() => {
this.loading = false
})
}
},
created() {
this.initQuery()
this.fetchRemark()
}
})
</script>
<style id="style-congress-meeting-remark-h5">
.meeting-remark-page {
min-height: calc(100vh - 46px);
padding: 16px;
box-sizing: border-box;
background: #f5f7fa;
}
.remark-content-card {
padding: 20px;
border-radius: 14px;
background: #fff;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
}
.remark-title {
margin: 0;
color: #323233;
font-size: 17px;
line-height: 1.5;
}
.remark-divider {
height: 1px;
margin: 16px 0;
background: #ebedf0;
}
.remark-content {
color: #646566;
font-size: 15px;
line-height: 1.8;
white-space: pre-wrap;
word-break: break-word;
}
</style>
<!--#
}
#-->
@@ -0,0 +1,254 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<div class="pdf-viewer-page">
<van-nav-bar :title="fileName || '文件预览'" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<div class="pdf-container" :class="{'has-footer': voteInfo}">
<div v-if="previewMode === 'pdf'" id="congress-pdf-container" class="pdf-render-box"></div>
<img v-else-if="previewMode === 'image'" :src="previewUrl" class="image-preview" @click="previewImage">
<iframe v-else-if="previewMode === 'iframe'" :src="previewUrl" class="pdf-frame"></iframe>
<div v-else class="pdf-empty">
<van-empty description="暂无可预览附件"></van-empty>
</div>
</div>
<div v-if="voteInfo" class="footer-bar">
<van-button type="primary" block @click="handleVoteClick">{{ isVoted(voteInfo.voted) ? '查看投票' : '投票' }}</van-button>
</div>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
pdfPath: "",
fileName: "",
fileId: "",
materialsId: "",
sysFileId: "",
fileType: "",
previewUrl: "",
previewMode: "empty",
pdfObj: null,
voteInfo: null
}
},
computed: {
fileSuffix() {
const name = this.fileName || ""
const url = (this.pdfPath || "").split("?")[0]
const source = name.indexOf(".") > -1 ? name : url
const index = source.lastIndexOf(".")
return index > -1 ? source.substring(index + 1).toLowerCase() : ""
}
},
methods: {
initQuery() {
const params = new URLSearchParams(window.location.search)
this.pdfPath = params.get("pdfPath") || ""
this.fileName = params.get("fileName") || ""
this.fileId = params.get("fileId") || ""
this.materialsId = params.get("materialsId") || params.get("materialId") || this.fileId
this.sysFileId = params.get("sysFileId") || ""
this.fileType = params.get("fileType") || ""
},
initPreview() {
if (!this.pdfPath) {
this.previewMode = "empty"
return
}
const suffix = this.resolveSuffix()
if (this.isImage(suffix)) {
this.previewUrl = this.pdfPath
this.previewMode = "image"
return
}
if (suffix === "pdf" || !suffix) {
this.previewUrl = this.pdfPath
this.previewMode = "pdf"
this.renderPdf(this.previewUrl)
return
}
if (this.isOfficeFile(suffix)) {
const fileId = this.resolveFileId()
if (fileId) {
this.previewUrl = "/assets/platform/plugins/pdfJs/web/viewer.html?file=" + encodeURIComponent("/platform/sys/file/convertPDF?id=" + fileId)
this.previewMode = "iframe"
return
}
}
this.previewUrl = this.pdfPath
this.previewMode = "iframe"
},
resolveSuffix() {
if (this.fileSuffix) return this.fileSuffix
const type = String(this.fileType || "").replace(".", "").toLowerCase()
if (["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "jpg", "jpeg", "png", "gif", "bmp", "webp"].indexOf(type) > -1) {
return type
}
if (type.indexOf("pdf") > -1) return "pdf"
if (type.indexOf("word") > -1) return "docx"
if (type.indexOf("excel") > -1 || type.indexOf("sheet") > -1) return "xlsx"
if (type.indexOf("powerpoint") > -1 || type.indexOf("presentation") > -1) return "pptx"
if (type.indexOf("image") > -1) return "jpg"
return ""
},
resolveFileId() {
if (this.sysFileId) return this.sysFileId
if (!this.pdfPath) return ""
try {
const url = new URL(this.pdfPath, window.location.origin)
return url.searchParams.get("id") || ""
} catch (e) {
const match = this.pdfPath.match(/[?&]id=([^&]+)/)
return match ? decodeURIComponent(match[1]) : ""
}
},
isImage(suffix) {
return ["jpg", "jpeg", "png", "gif", "bmp", "webp"].indexOf(suffix) > -1
},
isOfficeFile(suffix) {
return ["doc", "docx", "xls", "xlsx", "ppt", "pptx"].indexOf(suffix) > -1
},
renderPdf(url) {
this.$nextTick(() => {
const container = document.getElementById("congress-pdf-container")
if (!container || !url) return
container.innerHTML = ""
try {
if (this.pdfObj && this.pdfObj.destroy) {
this.pdfObj.destroy()
}
} catch (e) {}
try {
this.pdfObj = new Pdfh5("#congress-pdf-container", {
pdfurl: url,
lazy: true
})
} catch (e) {
this.previewUrl = "/assets/platform/plugins/pdfJs/web/viewer.html?file=" + encodeURIComponent(url)
this.previewMode = "iframe"
}
})
},
previewImage() {
if (!this.previewUrl) return
vant.ImagePreview({
images: [this.previewUrl],
closeable: true
})
},
fetchVoteInfo() {
if (!this.materialsId) return
const repId = window.localStorage.getItem("repId") || ""
this.$axios.get("/platform/h5/congress/votingIssue", {
params: {materialsId: this.materialsId, repId: repId}
}).then((res) => {
if (res.code === 0 && res.data) {
this.voteInfo = res.data
}
})
},
handleVoteClick() {
if (!this.voteInfo) {
this.$toast("投票信息未加载")
return
}
window.localStorage.setItem("voteInfo", JSON.stringify(this.voteInfo))
this.$pjaxReplace(this.isVoted(this.voteInfo.voted) ? "/platform/h5/congress/voted" : "/platform/h5/congress/vote")
},
isVoted(value) {
return value === true || value === 1 || value === "1" || value === "true"
}
},
created() {
this.initQuery()
this.fetchVoteInfo()
this.initPreview()
}
})
</script>
<style id="style-congress-pdf-h5">
.pdf-viewer-page {
min-height: 100vh;
background: #f5f6f8;
}
.pdf-container {
height: calc(100vh - 46px);
background: #f5f6f8;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.pdf-container.has-footer {
height: calc(100vh - 110px);
padding-bottom: 64px;
}
.pdf-frame {
width: 100%;
height: 100%;
border: 0;
background: #fff;
}
.pdf-render-box {
width: 100%;
min-height: 100%;
background: #f5f6f8;
}
.pdf-render-box .pdfjs {
background: #f5f6f8;
}
.image-preview {
display: block;
width: 100%;
height: auto;
min-height: 100%;
object-fit: contain;
background: #fff;
}
.pdf-empty {
width: 100%;
height: 100%;
background: #f5f6f8;
display: flex;
align-items: center;
justify-content: center;
}
.footer-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
background: #fff;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
}
.footer-bar .van-button {
height: 46px;
border-radius: 10px;
background: #1989fa;
border: 0;
font-size: 15px;
font-weight: 600;
box-shadow: 0 4px 12px rgba(25, 137, 250, 0.3);
}
</style>
<!--#
}
#-->
@@ -0,0 +1,445 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="进行投票" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<div class="vote-page">
<div class="vote-content">
<div class="vote-header-card">
<h2 class="vote-title">{{ voteInfo.name }}</h2>
<div v-if="voteTicketName" class="vote-type-text">{{ voteTicketName }}</div>
<div class="vote-meta">
<div class="meta-item">
<van-icon name="completed" size="14"></van-icon>
<span>{{ sessionName }}</span>
</div>
<!-- <div class="meta-divider"></div>-->
<!-- <div class="meta-item">-->
<!-- <van-icon name="clock-o" size="14"></van-icon>-->
<!-- <span>{{ timeName }}</span>-->
<!-- </div>-->
</div>
</div>
<div v-if="voteItems.length > 0">
<div v-if="voteItems.length === 1" class="vote-card-mode">
<div class="vote-item-card">
<div class="card-header">
<span class="item-name">{{ voteItems[0].itemName }}</span>
</div>
<div class="card-options">
<label v-for="option in voteItems[0].options" :key="option" class="option-label">
<input type="radio" name="vote-0" :value="option" v-model="selectedVotes[0]">
<span class="option-text">{{ option }}</span>
</label>
</div>
</div>
</div>
<div v-else class="vote-table-container">
<table class="vote-table">
<thead>
<tr>
<th class="col-name">{{ voteInfo.votingContentItemHeader || '表决事项' }}</th>
<th class="col-evaluate" colspan="2">{{ voteInfo.votingContentOptionHeader || '表决内容' }}</th>
</tr>
</thead>
<tbody>
<template v-for="(item, index) in voteItems">
<tr v-for="rowIndex in getRowCount(item)" :key="'row-' + index + '-' + rowIndex">
<td v-if="rowIndex === 1" class="col-name" :rowspan="getRowCount(item)">{{ item.itemName }}</td>
<td class="col-option" v-for="optIndex in 2" :key="'opt-' + optIndex">
<template v-if="(rowIndex - 1) * 2 + optIndex - 1 < item.options.length">
<label class="radio-label">
<input
type="radio"
:name="'vote-' + index"
:value="item.options[(rowIndex - 1) * 2 + optIndex - 1]"
v-model="selectedVotes[index]">
<span class="radio-text">{{ item.options[(rowIndex - 1) * 2 + optIndex - 1] }}</span>
</label>
</template>
<span v-else class="empty-placeholder"></span>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<div v-if="voteInfo.description" class="vote-description">
<div class="description-header">
<van-icon name="info-o" size="16"></van-icon>
<span>投票说明</span>
</div>
<div class="description-content">
<p v-for="(line, index) in descriptionLines" :key="index" class="description-line">{{ line }}</p>
</div>
</div>
<van-empty v-if="voteItems.length === 0" description="暂无投票内容"></van-empty>
</div>
<div class="vote-footer">
<van-button type="primary" block @click="submitVote">确认投票</van-button>
</div>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
voteInfo: {},
voteItems: [],
selectedVotes: {},
sessionName: "",
timeName: ""
}
},
computed: {
voteTicketName() {
const typeName = this.voteInfo.typeName || ""
if (!typeName) return ""
return typeName.indexOf("票") > -1 ? typeName : typeName + "票"
},
descriptionLines() {
if (!this.voteInfo.description) return []
return String(this.voteInfo.description).split("\n").filter((line) => line.trim() !== "")
}
},
methods: {
initVoteInfo() {
const cached = window.localStorage.getItem("voteInfo")
this.sessionName = window.localStorage.getItem("sessionName") || ""
this.timeName = window.localStorage.getItem("timeName") || ""
if (!cached) return
try {
this.voteInfo = JSON.parse(cached) || {}
} catch (e) {
this.voteInfo = {}
}
this.sessionName = this.sessionName || this.voteInfo.sessionName || ""
this.timeName = this.timeName || this.voteInfo.timeName || ""
this.parseVoteItems()
},
parseVoteItems() {
if (!this.voteInfo.votingContent) return
let content = this.voteInfo.votingContent
try {
if (typeof content === "string") content = JSON.parse(content)
} catch (e) {
this.voteItems = []
return
}
const list = Array.isArray(content) ? content : (content.items || [content])
this.voteItems = list.map((item, index) => {
let options = item.options || []
if (item.selections && typeof item.selections === "object") {
options = Object.keys(item.selections)
}
if (!Array.isArray(options) || options.length === 0) {
options = ["赞成", "不赞成", "弃权"]
}
return {
itemName: item.itemName || item.item || item.name || item.title || ("投票项" + (index + 1)),
options: options.map((option) => typeof option === "string" ? option : (option.label || option.name || option.value))
}
})
this.selectedVotes = {}
this.voteItems.forEach((_, index) => this.$set(this.selectedVotes, index, ""))
},
getRowCount(item) {
return Math.ceil(item.options.length / 2)
},
submitVote() {
const missing = this.voteItems.filter((_, index) => !this.selectedVotes[index])
if (missing.length > 0) {
this.$toast("请完成所有投票项的选择")
return
}
const voteResult = this.voteItems.map((item, index) => ({
item: item.itemName,
result: this.selectedVotes[index]
}))
this.$dialog.confirm({
title: "确认投票",
message: "您选择了 " + this.voteItems.length + " 项评价,确定要提交吗?",
confirmButtonText: "确认",
cancelButtonText: "取消",
confirmButtonColor: "#1989fa"
}).then(() => {
const body = new URLSearchParams()
body.append("issueId", this.voteInfo.id || "")
body.append("repId", window.localStorage.getItem("repId") || "")
body.append("sessionId", this.voteInfo.sessionId || "")
body.append("timeId", this.voteInfo.timeId || "")
body.append("delegationId", this.voteInfo.delegationId || "")
body.append("voteResult", JSON.stringify(voteResult))
return this.$axios.post("/platform/h5/congress/api/vote", body).then((res) => {
if (res.code === 0) {
this.$toast.success("投票成功")
this.voteInfo.voted = true
this.voteInfo.voteResult = JSON.stringify(voteResult)
window.localStorage.setItem("voteInfo", JSON.stringify(this.voteInfo))
window.localStorage.setItem("voteStatusUpdated", "true")
setTimeout(() => this.historyBack(), 800)
}
})
}).catch(() => {})
}
},
created() {
this.initVoteInfo()
}
})
</script>
<style id="style-congress-vote-h5">
.vote-page {
min-height: calc(100vh - 46px);
background: #f5f7fa;
display: flex;
flex-direction: column;
}
.vote-content {
flex: 1;
overflow-y: auto;
padding: 16px 16px 96px;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
}
.vote-header-card {
background: #fff;
margin-bottom: 16px;
padding: 20px;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
border: 1px solid rgba(0, 0, 0, 0.03);
}
.vote-title {
font-size: 18px;
font-weight: 600;
color: #323233;
margin: 0 0 8px;
line-height: 1.4;
text-align: center;
}
.vote-type-text {
width: 100%;
font-size: 14px;
color: #969799;
margin-bottom: 12px;
line-height: 1.5;
text-align: center;
}
.vote-meta {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding-top: 8px;
border-top: 1px solid #ebedf0;
}
.meta-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: #646566;
font-weight: 500;
}
.meta-item .van-icon {
color: #1989fa;
}
.meta-divider {
width: 1px;
height: 16px;
background: #e5e5e5;
}
.vote-table-container {
width: 100%;
overflow: hidden;
margin-bottom: 16px;
background: #fff;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
border: 1px solid #000;
}
.vote-table {
width: 100%;
border-collapse: collapse;
background: #fff;
}
.vote-table th {
background: linear-gradient(135deg, #f0f5ff 0%, #e6f0ff 100%);
border: 1px solid #000;
padding: 14px 10px;
font-weight: 600;
font-size: 14px;
color: #1989fa;
text-align: center;
}
.vote-table td {
border: 1px solid #000;
padding: 12px 10px;
text-align: center;
vertical-align: middle;
font-size: 14px;
color: #646566;
}
.vote-table .col-name {
width: 88px;
min-width: 88px;
background: #f7f8fa;
color: #323233;
font-weight: 500;
}
.radio-label {
display: flex;
align-items: center;
justify-content: flex-start;
white-space: nowrap;
cursor: pointer;
padding-left: 8px;
}
.radio-label input {
width: 16px;
height: 16px;
margin-right: 8px;
accent-color: #1989fa;
}
.radio-text {
font-size: 14px;
color: #646566;
}
.vote-card-mode,
.vote-description {
margin-bottom: 16px;
}
.vote-item-card,
.vote-description {
background: #fff;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
}
.vote-item-card .card-header {
padding: 18px;
background: linear-gradient(135deg, #f0f5ff 0%, #e6f0ff 100%);
color: #1989fa;
font-weight: 600;
}
.card-options {
padding: 18px;
}
.option-label {
display: flex;
align-items: center;
padding: 12px;
background: #f7f8fa;
border-radius: 10px;
margin-bottom: 10px;
}
.option-label input {
margin-right: 10px;
accent-color: #1989fa;
}
.vote-description {
padding: 20px;
}
.description-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
font-size: 15px;
font-weight: 600;
color: #1989fa;
}
.description-line {
margin: 0 0 8px;
font-size: 14px;
line-height: 1.8;
color: #646566;
}
.vote-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
background: #fff;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
}
.vote-footer .van-button {
height: 48px;
border-radius: 12px;
font-weight: 600;
font-size: 15px;
background: linear-gradient(135deg, #1989fa 0%, #0d6efd 100%);
border: 0;
box-shadow: 0 4px 12px rgba(25, 137, 250, 0.3);
}
.vote-table-container {
border-color: #dfe5ee;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(30, 48, 80, 0.04);
}
.vote-table {
border-collapse: separate;
border-spacing: 0;
table-layout: fixed;
}
.vote-table th,
.vote-table td {
border: 0;
border-right: 1px solid #dfe5ee;
border-bottom: 1px solid #dfe5ee;
}
.vote-table tr > :last-child {
border-right: 0;
}
.vote-table tbody tr:last-child td {
border-bottom: 0;
}
</style>
<!--#
}
#-->
@@ -0,0 +1,435 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="查看投票" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<div class="vote-page">
<div class="vote-content">
<div class="vote-header-card">
<h2 class="vote-title">{{ voteInfo.name }}</h2>
<div v-if="voteTicketName" class="vote-type-text">{{ voteTicketName }}</div>
<div class="vote-meta">
<div class="meta-item">
<van-icon name="completed" size="14"></van-icon>
<span>{{ sessionName }}</span>
</div>
<!-- <div class="meta-divider"></div>-->
<!-- <div class="meta-item">-->
<!-- <van-icon name="clock-o" size="14"></van-icon>-->
<!-- <span>{{ timeName }}</span>-->
<!-- </div>-->
</div>
</div>
<div v-if="voteItems.length > 0">
<div v-if="voteItems.length === 1" class="vote-card-mode">
<div class="vote-item-card">
<div class="card-header">
<span class="item-name">{{ voteItems[0].itemName }}</span>
</div>
<div class="card-options">
<label v-for="option in voteItems[0].options" :key="option" class="option-label readonly-option">
<input type="radio" disabled :checked="selectedVotes[0] === option">
<span class="option-text">{{ option }}</span>
</label>
</div>
</div>
</div>
<div v-else class="vote-table-container">
<table class="vote-table">
<thead>
<tr>
<th class="col-name">{{ voteInfo.votingContentItemHeader || '表决事项' }}</th>
<th class="col-evaluate" colspan="2">{{ voteInfo.votingContentOptionHeader || '表决内容' }}</th>
</tr>
</thead>
<tbody>
<template v-for="(item, index) in voteItems">
<tr v-for="rowIndex in getRowCount(item)" :key="'row-' + index + '-' + rowIndex">
<td v-if="rowIndex === 1" class="col-name" :rowspan="getRowCount(item)">{{ item.itemName }}</td>
<td class="col-option" v-for="optIndex in 2" :key="'opt-' + optIndex">
<template v-if="(rowIndex - 1) * 2 + optIndex - 1 < item.options.length">
<label class="radio-label readonly-option">
<input
type="radio"
disabled
:checked="selectedVotes[index] === item.options[(rowIndex - 1) * 2 + optIndex - 1]">
<span class="radio-text">{{ item.options[(rowIndex - 1) * 2 + optIndex - 1] }}</span>
</label>
</template>
<span v-else class="empty-placeholder"></span>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<div v-if="voteInfo.description" class="vote-description">
<div class="description-header">
<van-icon name="info-o" size="16"></van-icon>
<span>投票说明</span>
</div>
<div class="description-content">
<p v-for="(line, index) in descriptionLines" :key="index" class="description-line">{{ line }}</p>
</div>
</div>
<van-empty v-if="voteItems.length === 0" description="暂无投票结果"></van-empty>
</div>
<div class="vote-footer">
<van-button type="primary" block disabled>已完成投票</van-button>
</div>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
voteInfo: {},
voteItems: [],
selectedVotes: {},
sessionName: "",
timeName: ""
}
},
computed: {
voteTicketName() {
const typeName = this.voteInfo.typeName || ""
if (!typeName) return ""
return typeName.indexOf("票") > -1 ? typeName : typeName + "票"
},
descriptionLines() {
if (!this.voteInfo.description) return []
return String(this.voteInfo.description).split("\n").filter((line) => line.trim() !== "")
}
},
methods: {
initVoteInfo() {
const cached = window.localStorage.getItem("voteInfo")
this.sessionName = window.localStorage.getItem("sessionName") || ""
this.timeName = window.localStorage.getItem("timeName") || ""
if (!cached) return
try {
this.voteInfo = JSON.parse(cached) || {}
} catch (e) {
this.voteInfo = {}
}
this.sessionName = this.sessionName || this.voteInfo.sessionName || ""
this.timeName = this.timeName || this.voteInfo.timeName || ""
this.parseVoteItems()
this.parseVoteResult()
},
parseVoteItems() {
if (!this.voteInfo.votingContent) return
let content = this.voteInfo.votingContent
try {
if (typeof content === "string") content = JSON.parse(content)
} catch (e) {
this.voteItems = []
return
}
const list = Array.isArray(content) ? content : (content.items || [content])
this.voteItems = list.map((item, index) => {
let options = item.options || []
if (item.selections && typeof item.selections === "object") {
options = Object.keys(item.selections)
}
if (!Array.isArray(options) || options.length === 0) {
options = ["赞成", "不赞成", "弃权"]
}
return {
itemName: item.itemName || item.item || item.name || item.title || ("投票项" + (index + 1)),
options: options.map((option) => typeof option === "string" ? option : (option.label || option.name || option.value))
}
})
},
parseVoteResult() {
let result = this.voteInfo.voteResult || []
try {
if (typeof result === "string") result = JSON.parse(result || "[]")
} catch (e) {
result = []
}
const resultMap = {}
if (Array.isArray(result)) {
result.forEach((item) => {
const name = item.item || item.itemName || item.name || item.title
const value = item.result || item.vote || item.value
if (name) resultMap[name] = value
})
} else if (result && typeof result === "object") {
Object.keys(result).forEach((key) => resultMap[key] = result[key])
}
this.selectedVotes = {}
this.voteItems.forEach((item, index) => {
this.$set(this.selectedVotes, index, resultMap[item.itemName] || "")
})
},
getRowCount(item) {
return Math.ceil(item.options.length / 2)
}
},
created() {
this.initVoteInfo()
}
})
</script>
<style id="style-congress-voted-h5">
.vote-page {
min-height: calc(100vh - 46px);
background: #f5f7fa;
display: flex;
flex-direction: column;
}
.vote-content {
flex: 1;
overflow-y: auto;
padding: 16px 16px 96px;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
}
.vote-header-card {
background: #fff;
margin-bottom: 16px;
padding: 20px;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
border: 1px solid rgba(0, 0, 0, 0.03);
}
.vote-title {
font-size: 18px;
font-weight: 600;
color: #323233;
margin: 0 0 8px;
line-height: 1.4;
text-align: center;
}
.vote-type-text {
width: 100%;
font-size: 14px;
color: #969799;
margin-bottom: 12px;
line-height: 1.5;
text-align: center;
}
.vote-meta {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding-top: 8px;
border-top: 1px solid #ebedf0;
}
.meta-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: #646566;
font-weight: 500;
}
.meta-item .van-icon {
color: #1989fa;
}
.meta-divider {
width: 1px;
height: 16px;
background: #e5e5e5;
}
.vote-table-container {
width: 100%;
overflow: hidden;
margin-bottom: 16px;
background: #fff;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
border: 1px solid #000;
}
.vote-table {
width: 100%;
border-collapse: collapse;
background: #fff;
}
.vote-table th {
background: linear-gradient(135deg, #f0f5ff 0%, #e6f0ff 100%);
border: 1px solid #000;
padding: 14px 10px;
font-weight: 600;
font-size: 14px;
color: #1989fa;
text-align: center;
}
.vote-table td {
border: 1px solid #000;
padding: 12px 10px;
text-align: center;
vertical-align: middle;
font-size: 14px;
color: #646566;
}
.vote-table .col-name {
width: 88px;
min-width: 88px;
background: #f7f8fa;
color: #323233;
font-weight: 500;
}
.radio-label {
display: flex;
align-items: center;
justify-content: flex-start;
white-space: nowrap;
padding-left: 8px;
}
.radio-label input {
width: 16px;
height: 16px;
margin-right: 8px;
accent-color: #1989fa;
opacity: 1;
}
.radio-text {
font-size: 14px;
color: #646566;
}
.vote-card-mode,
.vote-description {
margin-bottom: 16px;
}
.vote-item-card,
.vote-description {
background: #fff;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
}
.vote-item-card .card-header {
padding: 18px;
background: linear-gradient(135deg, #f0f5ff 0%, #e6f0ff 100%);
color: #1989fa;
font-weight: 600;
}
.card-options {
padding: 18px;
}
.option-label {
display: flex;
align-items: center;
padding: 12px;
background: #f7f8fa;
border-radius: 10px;
margin-bottom: 10px;
}
.option-label input {
margin-right: 10px;
accent-color: #1989fa;
opacity: 1;
}
.vote-description {
padding: 20px;
}
.description-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
font-size: 15px;
font-weight: 600;
color: #1989fa;
}
.description-line {
margin: 0 0 8px;
font-size: 14px;
line-height: 1.8;
color: #646566;
}
.readonly-option {
cursor: default;
}
.vote-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
background: #fff;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
}
.vote-footer .van-button {
height: 48px;
border-radius: 12px;
font-weight: 600;
font-size: 15px;
color: #fff;
background: #7dbbff;
border: 0;
opacity: 1;
}
.vote-table-container {
border-color: #dfe5ee;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(30, 48, 80, 0.04);
}
.vote-table {
border-collapse: separate;
border-spacing: 0;
table-layout: fixed;
}
.vote-table th,
.vote-table td {
border: 0;
border-right: 1px solid #dfe5ee;
border-bottom: 1px solid #dfe5ee;
}
.vote-table tr > :last-child {
border-right: 0;
}
.vote-table tbody tr:last-child td {
border-bottom: 0;
}
</style>
<!--#
}
#-->