feat:浦发项目模块迁移-职代会h5端初始化

This commit is contained in:
2026-07-28 17:27:29 +08:00
parent 785b9a4b80
commit 704bc278fa
9 changed files with 3240 additions and 0 deletions
@@ -0,0 +1,373 @@
package com.budwk.app.zhgh.democratic.congress.h5controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
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 org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.util.Date;
@IocBean
@At("/platform/h5/congress")
@Ok("json:full")
public class CongressH5Controller {
@Inject
private Dao dao;
@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("/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) {
String loginName = StrUtil.blankToDefault(emplid, SecurityUtil.getUserLoginname());
Sql sql = Sqls.create("""
SELECT
t.id,
t.id AS timeId,
t.name,
t.year,
t.session_id AS sessionId,
t.session_name AS sessionName,
s.type,
r.id AS repId,
r.delegation_id AS delegationId,
r.delegation_name AS delegationName
FROM congress_times t
INNER JOIN (
SELECT id, time_id, delegation_id, delegation_name
FROM congress_rep
WHERE emplid = @emplid
AND status = 2
AND IFNULL(deleted, 0) = 0
GROUP BY id, time_id, delegation_id, delegation_name
) r ON t.id = r.time_id
INNER JOIN congress_sessions s ON t.session_id = s.id
WHERE IFNULL(t.deleted, 0) = 0
AND IFNULL(s.deleted, 0) = 0
ORDER BY t.create_time DESC, t.id DESC
""");
sql.setParam("emplid", loginName);
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At("/materialsTypeList")
@SaCheckLogin
public Result 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 IFNULL(deleted, 0) = 0
GROUP BY name
ORDER BY sortNo ASC, name ASC
""");
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At("/materialsList")
@SaCheckLogin
public Result materialsList(@Param("typeId") String typeId, @Param("timeId") String timeId, @Param("emplid") String emplid) {
String loginName = StrUtil.blankToDefault(emplid, SecurityUtil.getUserLoginname());
Sql sql = Sqls.create("""
SELECT
cm.id,
cm.session_id AS sessionId,
cm.time_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 congress_materials_type cmt ON cm.type_id = cmt.id AND IFNULL(cmt.deleted, 0) = 0
LEFT JOIN congress_voting_issue cvi ON cm.id = cvi.materials_id
AND IFNULL(cvi.deleted, 0) = 0
AND cmt.is_vote = true
LEFT JOIN congress_voting_record cvr ON cvi.id = cvr.issue_id
AND cvr.emplid = @emplid
AND IFNULL(cvr.deleted, 0) = 0
WHERE IFNULL(cm.deleted, 0) = 0
AND cm.type_id = @typeId
AND cm.time_id = @timeId
ORDER BY cm.sort_no ASC, cm.create_time DESC
""");
sql.setParam("typeId", typeId);
sql.setParam("timeId", timeId);
sql.setParam("emplid", loginName);
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At("/user/meetingList")
@SaCheckLogin
public Result userMeetingList(@Param("timeId") String timeId, @Param("emplid") String emplid) {
String loginName = StrUtil.blankToDefault(emplid, SecurityUtil.getUserLoginname());
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,
m.time_id AS timeId,
m.remark,
(
SELECT COUNT(1)
FROM congress_sign_in s
WHERE s.meeting_id = m.id
AND IFNULL(s.deleted, 0) = 0
) AS signInCount,
si.id AS signInId,
si.create_time AS userSignInTime,
r.id AS repId
FROM congress_sign_in_meeting m
LEFT JOIN congress_sign_in si ON m.id = si.meeting_id
AND IFNULL(si.deleted, 0) = 0
AND si.emplid = @emplid
LEFT JOIN congress_rep r ON r.time_id = m.time_id
AND r.emplid = @emplid
AND IFNULL(r.deleted, 0) = 0
WHERE IFNULL(m.deleted, 0) = 0
AND m.time_id = @timeId
ORDER BY m.sign_in_start_time DESC, m.create_time DESC
""");
sql.setParam("timeId", timeId);
sql.setParam("emplid", loginName);
sql.setCallback(Sqls.callback.maps());
dao.execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At("/signin")
@SaCheckLogin
public Result signin(@Param("repId") String repId,
@Param("sessionId") String sessionId,
@Param("timeId") String timeId,
@Param("meetingId") String meetingId) {
if (StrUtil.hasBlank(repId, sessionId, timeId, meetingId)) {
return Result.error("签到参数不完整");
}
int count = countBySql("""
SELECT COUNT(1)
FROM congress_sign_in
WHERE rep_id = @repId
AND meeting_id = @meetingId
AND IFNULL(deleted, 0) = 0
""", NutMap.NEW().addv("repId", repId).addv("meetingId", meetingId));
if (count > 0) {
return Result.success(true);
}
Sql repSql = Sqls.create("""
SELECT id, emplid, name, delegation_id AS delegationId
FROM congress_rep
WHERE id = @repId
AND IFNULL(deleted, 0) = 0
LIMIT 1
""");
repSql.setParam("repId", repId);
repSql.setCallback(Sqls.callback.map());
dao.execute(repSql);
NutMap rep = repSql.getObject(NutMap.class);
if (rep == null) {
return Result.error("未找到代表信息");
}
dao.insert("congress_sign_in", Chain.make("id", R.UU32())
.add("session_id", sessionId)
.add("time_id", timeId)
.add("meeting_id", meetingId)
.add("delegation_id", rep.getString("delegationId"))
.add("rep_id", repId)
.add("emplid", rep.getString("emplid"))
.add("name", rep.getString("name"))
.add("create_time", new Date())
.add("update_time", new Date())
.add("deleted", false));
return Result.success(true);
}
@At("/votingIssue")
@SaCheckLogin
public Result votingIssue(@Param("materialsId") String materialsId, @Param("repId") String repId) {
Sql sql = Sqls.create("""
SELECT
i.id,
i.session_id AS sessionId,
i.session_name AS sessionName,
i.time_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
LEFT JOIN congress_materials_type mt ON mt.id = i.type_id AND IFNULL(mt.deleted, 0) = 0
LEFT JOIN congress_voting_record vr ON vr.issue_id = i.id
AND vr.rep_id = @repId
AND IFNULL(vr.deleted, 0) = 0
WHERE i.materials_id = @materialsId
AND IFNULL(i.deleted, 0) = 0
LIMIT 1
""");
sql.setParam("materialsId", materialsId);
sql.setParam("repId", repId);
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
return Result.success(sql.getObject(NutMap.class));
}
@At("/api/vote")
@SaCheckLogin
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) {
if (StrUtil.hasBlank(issueId, repId, sessionId, timeId, voteResult)) {
return Result.error("投票参数不完整");
}
int count = countBySql("""
SELECT COUNT(1)
FROM congress_voting_record
WHERE issue_id = @issueId
AND rep_id = @repId
AND IFNULL(deleted, 0) = 0
""", NutMap.NEW().addv("issueId", issueId).addv("repId", repId));
if (count > 0) {
return Result.error("您已投票,请勿重复提交");
}
Sql repSql = Sqls.create("""
SELECT user_id AS userId, emplid, name
FROM congress_rep
WHERE id = @repId
AND IFNULL(deleted, 0) = 0
LIMIT 1
""");
repSql.setParam("repId", repId);
repSql.setCallback(Sqls.callback.map());
dao.execute(repSql);
NutMap rep = repSql.getObject(NutMap.class);
if (rep == null) {
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("emplid"))
.add("name", rep.getString("name"))
.add("session_id", sessionId)
.add("time_id", timeId)
.add("delegation_id", 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 int countBySql(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,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');
@@ -0,0 +1,372 @@
<!--#
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>
<p class="session-subtitle">{{ selectedTimeText }}</p>
</div>
</div>
<div class="module-list-container">
<div class="modules-wrapper">
<div
v-for="module in moduleList"
:key="module.id"
class="module-card"
@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>
<div v-if="timesList.length > 1" class="bottom-switch-bar">
<span class="switch-text" @click="showTimePicker = true">
<van-icon name="arrow-down" size="12"></van-icon>
切换届次
</span>
</div>
</template>
<van-popup v-model="showTimePicker" position="bottom">
<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: "",
selectedTimeText: "",
selectedSessionName: "",
selectedRepId: "",
showTimePicker: false,
moduleList: []
}
},
computed: {
formattedTimeColumns() {
return this.timesList.map((item) => ({
text: item.name,
value: item.id
}))
}
},
methods: {
async initPage() {
const loading = this.$toast.loading({message: "加载中...", forbidClick: true, duration: 0})
try {
await this.fetchRepTimes()
if (this.timesList.length > 0) {
this.selectTime(this.timesList[0])
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 || []
}
},
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: "calendar-o",
desc: "点击进行会议签到"
}].concat(modules)
},
selectTime(item) {
this.selectedTimeId = item.id || item.timeId || ""
this.selectedTimeText = item.name || ""
this.selectedSessionName = item.sessionName || ""
this.selectedRepId = item.repId || ""
window.localStorage.setItem("timeName", this.selectedTimeText)
window.localStorage.setItem("sessionName", this.selectedSessionName)
window.localStorage.setItem("repId", this.selectedRepId)
},
onTimeConfirm(value) {
const selected = this.timesList.find((item) => item.id === value.value)
if (selected) {
this.selectTime(selected)
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))
return
}
this.$pjaxReplace("/platform/h5/congress/materials/list?moduleId=" + encodeURIComponent(module.id) +
"&typeId=" + encodeURIComponent(module.id) +
"&moduleName=" + encodeURIComponent(module.name) +
"&timeId=" + encodeURIComponent(this.selectedTimeId))
},
resolveIcon(name) {
if (!name) return "orders-o"
if (name.indexOf("会议签到") > -1) return "calendar-o"
if (name.indexOf("评议") > -1) return "star"
if (name.indexOf("议题") > -1) return "todo-list-o"
if (name.indexOf("资料") > -1) return "notes-o"
if (name.indexOf("公告") > -1 || name.indexOf("结果") > -1) return "bullhorn-o"
if (name.indexOf("文件") > -1) return "description"
return "orders-o"
},
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 ""
}
},
created() {
this.initPage()
}
})
</script>
<style id="style-congress-h5">
.congress-page {
min-height: calc(100vh - 46px);
background: #f5f7fa;
}
.page-header {
background: linear-gradient(135deg, #1989fa 0%, #0d6efd 100%);
padding: 24px 16px 28px;
color: #fff;
box-shadow: 0 4px 16px rgba(25, 137, 250, 0.15);
display: flex;
justify-content: center;
align-items: center;
}
.session-info-card {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.session-title {
font-size: 20px;
font-weight: 600;
line-height: 1.4;
color: #fff;
margin: 0 0 8px;
}
.session-subtitle {
font-size: 15px;
line-height: 1.3;
color: #fff;
margin: 0;
}
.module-list-container {
padding: 16px;
}
.modules-wrapper {
display: flex;
flex-direction: column;
gap: 14px;
}
.module-card {
background: #fff;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
overflow: hidden;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid rgba(0, 0, 0, 0.03);
position: relative;
}
.module-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;
}
.module-card:hover {
box-shadow: 0 8px 24px rgba(25, 137, 250, 0.12);
transform: translateY(-3px);
border-color: rgba(25, 137, 250, 0.1);
}
.module-card:hover::before {
opacity: 1;
}
.module-card:active {
transform: translateY(-1px) scale(0.99);
box-shadow: 0 4px 16px rgba(25, 137, 250, 0.08);
}
.module-header {
padding: 16px 18px;
display: flex;
align-items: center;
gap: 14px;
}
.icon-wrapper {
width: 56px;
height: 56px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #e6f0ff 0%, #f0f5ff 100%);
flex-shrink: 0;
box-shadow: 0 2px 8px rgba(25, 137, 250, 0.12);
color: #1989fa;
}
.module-info {
flex: 1;
min-width: 0;
}
.module-title {
font-size: 17px;
font-weight: 600;
color: #4a4a4a;
margin: 0 0 6px;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.module-desc {
font-size: 13px;
color: #969799;
margin: 0;
line-height: 1.5;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.arrow-icon {
font-size: 18px;
color: #c8c9cc;
flex-shrink: 0;
transition: all 0.3s ease;
}
.module-card:hover .arrow-icon {
color: #1989fa;
transform: translateX(4px);
}
.bottom-switch-bar {
padding: 20px 16px calc(20px + env(safe-area-inset-bottom));
display: flex;
justify-content: center;
}
.switch-text {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 13px;
color: #969799;
cursor: pointer;
user-select: none;
}
.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,279 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar :title="pageTitle" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<div class="file-list-page">
<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 v-if="item.hasVoted !== null && item.hasVoted !== undefined" class="vote-badge">
<span v-if="isVoted(item.hasVoted)" class="badge voted">已投票</span>
<span v-else class="badge pending">待投票</span>
</div>
<div class="card-left">
<div class="icon-wrapper">
<van-icon :name="getFileIcon(item.fileType)" size="36"></van-icon>
</div>
</div>
<div class="card-content">
<div class="card-header">
<span class="file-name">{{ formatFileName(item.name || item.fileName) }}</span>
<van-icon name="arrow" class="arrow-icon"></van-icon>
</div>
<div v-if="item.uploadTime" class="file-info">
<div class="info-row">
<van-icon name="clock-o" size="14" color="#969799"></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="暂无文件"></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: ""
}
},
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") || ""
},
loginName() {
const user = (this.$store.state && this.$store.state.user) || JSON.parse(window.sessionStorage.getItem("user") || "{}")
return user.loginname || user.loginName || ""
},
onRefresh() {
this.fileList = []
this.finished = false
this.loadedOnce = false
this.fetchFileList()
},
onLoad() {
if (!this.loadedOnce) {
this.fetchFileList()
}
},
fetchFileList() {
this.loading = true
this.$axios.get("/platform/h5/congress/materialsList", {
params: {
typeId: this.typeId,
timeId: this.timeId,
emplid: this.loginName()
}
}).then((res) => {
if (res.code === 0) {
this.fileList = res.data || []
}
this.finished = true
this.loadedOnce = true
}).finally(() => {
this.loading = false
this.refreshing = false
})
},
getFileIcon(mimeType) {
if (!mimeType) return "description"
const type = String(mimeType).toLowerCase()
if (type.indexOf("pdf") > -1) return "description"
if (type.indexOf("word") > -1) return "edit"
if (type.indexOf("excel") > -1 || type.indexOf("sheet") > -1) return "chart-trending-o"
if (type.indexOf("powerpoint") > -1 || type.indexOf("presentation") > -1) return "play-circle-o"
if (type.indexOf("image") > -1) return "photo"
if (type.indexOf("zip") > -1 || type.indexOf("rar") > -1) return "bag-o"
return "description"
},
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 || ""))
},
openVotePdf(item) {
this.$pjaxReplace("/platform/h5/congress/pdf?pdfPath=" + encodeURIComponent(item.fileUrl || "") +
"&fileName=" + encodeURIComponent(item.fileName || item.name || "") +
"&fileId=" + encodeURIComponent(item.id || ""))
},
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-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:hover {
box-shadow: 0 8px 24px rgba(25, 137, 250, 0.12);
transform: translateY(-3px);
border-color: rgba(25, 137, 250, 0.1);
}
.file-card:hover::before {
opacity: 1;
}
.file-card:active {
transform: translateY(-1px) scale(0.99);
box-shadow: 0 4px 16px rgba(25, 137, 250, 0.08);
}
.vote-badge {
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.badge {
display: inline-block;
padding: 6px 14px;
font-size: 12px;
font-weight: 600;
border-bottom-right-radius: 12px;
color: #fff;
}
.badge.voted {
background: linear-gradient(135deg, #52c41a 0%, #389e0d 100%);
}
.badge.pending {
background: linear-gradient(135deg, #faad14 0%, #d48806 100%);
}
.card-left {
flex-shrink: 0;
margin-top: 12px;
}
.icon-wrapper {
width: 52px;
height: 52px;
border-radius: 14px;
background: linear-gradient(135deg, #f0f5ff 0%, #e6f0ff 100%);
display: flex;
align-items: center;
justify-content: center;
color: #1989fa;
}
.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;
}
.info-value {
color: #646566;
font-weight: 500;
}
</style>
<!--#
}
#-->
@@ -0,0 +1,306 @@
<!--#
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="meeting-list-page">
<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">
<div class="card-left">
<div class="icon-wrapper" :class="{'signed-icon': item.signInId}">
<van-icon name="calendar-o" size="26"></van-icon>
</div>
</div>
<div class="card-content">
<div class="card-header">
<span class="meeting-name">{{ item.meetingName }}</span>
</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">{{ formatTimeRange(item.signInStartTime, item.signInEndTime) }}</span></span>
</div>
<div v-if="item.userSignInTime" class="info-row signed">
<van-icon name="checked" size="14" color="#07c160"></van-icon>
<span class="info-text">已签到:<span class="info-value">{{ formatTime(item.userSignInTime) }}</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.signInId" class="action-section">
<van-button v-if="!canSignIn(item)" disabled type="default" class="signin-btn disabled">
<van-icon name="clock-o" size="16"></van-icon>
{{ getSignInButtonText(item) }}
</van-button>
<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="暂无会议"></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: ""
}
},
methods: {
initQuery() {
const params = new URLSearchParams(window.location.search)
this.timeId = params.get("timeId") || ""
},
loginName() {
const user = (this.$store.state && this.$store.state.user) || JSON.parse(window.sessionStorage.getItem("user") || "{}")
return user.loginname || user.loginName || ""
},
onRefresh() {
this.meetingList = []
this.finished = false
this.loadedOnce = false
this.fetchMeetingList()
},
onLoad() {
if (!this.loadedOnce) {
this.fetchMeetingList()
}
},
fetchMeetingList() {
this.loading = true
this.$axios.get("/platform/h5/congress/user/meetingList", {
params: {timeId: this.timeId, emplid: this.loginName()}
}).then((res) => {
if (res.code === 0) {
this.meetingList = res.data || []
}
this.finished = true
this.loadedOnce = true
}).finally(() => {
this.loading = false
this.refreshing = false
})
},
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-card {
background: #fff;
margin: 16px;
padding: 24px;
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: flex-start;
gap: 18px;
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;
}
.meeting-card:hover {
box-shadow: 0 8px 24px rgba(25, 137, 250, 0.12);
transform: translateY(-3px);
border-color: rgba(25, 137, 250, 0.1);
}
.meeting-card:hover::before {
opacity: 1;
}
.card-left {
flex-shrink: 0;
padding-top: 2px;
}
.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;
}
.card-content {
flex: 1;
min-width: 0;
}
.card-header {
margin-bottom: 12px;
}
.meeting-name {
font-size: 17px;
color: #323233;
font-weight: 600;
line-height: 1.5;
word-break: break-word;
}
.info-list {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 12px;
}
.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.signed .info-text {
color: #07c160;
}
.info-row.count .info-text {
color: #1989fa;
}
.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);
}
.signin-btn.disabled {
background: linear-gradient(135deg, rgba(25, 137, 250, 0.3) 0%, rgba(13, 110, 253, 0.3) 100%);
color: #fff;
border: 1px solid rgba(25, 137, 250, 0.4);
opacity: 0.7;
}
</style>
<!--#
}
#-->
@@ -0,0 +1,140 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<div class="pdf-viewer-page">
<van-nav-bar :title="fileName || 'PDF查看'" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<div class="pdf-container" :class="{'has-footer': voteInfo}">
<iframe v-if="pdfPath" :src="previewUrl" class="pdf-frame"></iframe>
<div v-else class="pdf-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: "",
voteInfo: null
}
},
computed: {
previewUrl() {
if (!this.pdfPath) return ""
return this.pdfPath
}
},
methods: {
initQuery() {
const params = new URLSearchParams(window.location.search)
this.pdfPath = params.get("pdfPath") || ""
this.fileName = params.get("fileName") || ""
this.fileId = params.get("fileId") || ""
},
fetchVoteInfo() {
if (!this.fileId) return
const repId = window.localStorage.getItem("repId") || ""
this.$axios.get("/platform/h5/congress/votingIssue", {
params: {materialsId: this.fileId, 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()
}
})
</script>
<style id="style-congress-pdf-h5">
.pdf-viewer-page {
min-height: 100vh;
background: #f5f6f8;
}
.pdf-viewer-page .van-nav-bar {
background: #1989fa;
box-shadow: 0 4px 16px rgba(25, 137, 250, 0.15);
}
.pdf-viewer-page .van-nav-bar__title,
.pdf-viewer-page .van-nav-bar .van-icon,
.pdf-viewer-page .van-nav-bar__text {
color: #fff;
font-weight: 600;
}
.pdf-container {
height: calc(100vh - 46px);
background: #f5f6f8;
overflow: hidden;
}
.pdf-container.has-footer {
height: calc(100vh - 110px);
padding-bottom: 64px;
}
.pdf-frame {
width: 100%;
height: 100%;
border: 0;
background: #fff;
}
.pdf-empty {
width: 100%;
height: 100%;
background: #f5f6f8;
}
.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,417 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<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="calendar-o" 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: 100vh;
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;
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;
border-left: 4px solid #1989fa;
}
.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);
}
</style>
<!--#
}
#-->
@@ -0,0 +1,407 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<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="calendar-o" 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: 100vh;
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;
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;
border-left: 4px solid #1989fa;
}
.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;
}
</style>
<!--#
}
#-->