Revert "新增活动电子档案模块;"

This reverts commit a8a4052c45.
This commit is contained in:
2026-07-28 17:42:37 +08:00
parent d90f87e453
commit a93cbc9704
11 changed files with 0 additions and 1975 deletions
@@ -1,62 +0,0 @@
package com.budwk.app.zhgh.archive.constant;
import org.nutz.lang.Strings;
import java.util.List;
import java.util.Map;
/**
* 活动电子档案常量。
*/
public final class ArchiveConstant {
public static final int PROJECT_DRAFT = 0;
public static final int PROJECT_FILING = 1;
public static final int PROJECT_COMPLETED = 2;
public static final int DOCUMENT_DRAFT = 0;
public static final int DOCUMENT_SUBMITTED = 1;
public static final String STAGE_PREPARE = "PREPARE";
public static final String STAGE_PROCESS = "PROCESS";
public static final String STAGE_SUMMARY = "SUMMARY";
public static final Map<String, String> STAGE_LABELS = Map.of(
STAGE_PREPARE, "筹备阶段",
STAGE_PROCESS, "实施阶段",
STAGE_SUMMARY, "总结阶段"
);
private ArchiveConstant() {
}
public static boolean validStage(String stage) {
return Strings.isNotBlank(stage) && STAGE_LABELS.containsKey(stage);
}
public static String stageLabel(String stage) {
return STAGE_LABELS.getOrDefault(stage, stage);
}
public static String projectStatusLabel(Integer status) {
if (status != null && status == PROJECT_COMPLETED) {
return "已完成";
}
if (status != null && status == PROJECT_FILING) {
return "归档中";
}
return "草稿";
}
public static String documentStatusLabel(Integer status) {
return status != null && status == DOCUMENT_SUBMITTED ? "已提交归档" : "草稿";
}
public static List<Map<String, String>> stageOptions() {
return List.of(
Map.of("value", STAGE_PREPARE, "label", STAGE_LABELS.get(STAGE_PREPARE)),
Map.of("value", STAGE_PROCESS, "label", STAGE_LABELS.get(STAGE_PROCESS)),
Map.of("value", STAGE_SUMMARY, "label", STAGE_LABELS.get(STAGE_SUMMARY))
);
}
}
@@ -1,221 +0,0 @@
package com.budwk.app.zhgh.archive.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
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.archive.constant.ArchiveConstant;
import com.budwk.app.zhgh.archive.model.ArchiveDocType;
import com.budwk.app.zhgh.archive.model.ArchiveDocument;
import com.budwk.app.zhgh.archive.model.ArchiveProject;
import com.budwk.app.zhgh.archive.service.ArchiveService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
/**
* 活动电子档案。
*/
@IocBean
@Ok("json:full")
@At("/platform/archive")
@Api(tags = "活动电子档案")
public class ArchiveController {
@Inject
private ArchiveService archiveService;
@Inject
private Dao dao;
@At("/docType")
@Ok("beetl:/platform/zhgh/archive/docType/index.html")
@SaCheckPermission("archive.docType")
public void docTypeIndex() {
}
@At("/docType/pageData")
@SaCheckPermission("archive.docType")
public Result docTypePageData(PageForm pageForm, String name,
String projectStage, Boolean requiredFlag) {
return Result.success(archiveService.pageDocTypes(
pageForm, name, projectStage, requiredFlag));
}
@At("/docType/listData")
public Result docTypeListData(String projectStage) {
return Result.success(archiveService.listDocTypes(projectStage));
}
@At("/docType/fetchOne")
@SaCheckPermission("archive.docType")
public Result docTypeFetchOne(String id) {
return Result.success(dao.fetch(ArchiveDocType.class, id));
}
@At("/docType/onSubmit")
@SLog(tag = "活动电子档案-文档类型", msg = "新增或修改文档类型")
@SaCheckPermission("archive.docType")
public Result docTypeOnSubmit(ArchiveDocType docType) {
try {
archiveService.saveDocType(docType);
return Result.success();
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
@At("/docType/onDelete")
@SLog(tag = "活动电子档案-文档类型", msg = "删除文档类型")
@SaCheckPermission("archive.docType")
public Result docTypeOnDelete(String id) {
try {
archiveService.deleteDocType(id);
return Result.success();
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
@At("/project")
@Ok("beetl:/platform/zhgh/archive/project/index.html")
@SaCheckPermission("archive.project")
public void projectIndex() {
}
@At("/project/pageData")
@SaCheckPermission("archive.project")
public Result projectPageData(PageForm pageForm, Integer year,
String projectName, String unionId, Integer status) {
return Result.success(archiveService.pageProjects(
pageForm, year, projectName, unionId, status));
}
@At("/project/listData")
public Result projectListData() {
return Result.success(archiveService.listProjects());
}
@At("/project/fetchOne")
@SaCheckPermission("archive.project")
public Result projectFetchOne(String id) {
return Result.success(dao.fetch(ArchiveProject.class, id));
}
@At("/project/onSubmit")
@SLog(tag = "活动电子档案-归档项目", msg = "新增或修改归档项目")
@SaCheckPermission("archive.project")
public Result projectOnSubmit(ArchiveProject project) {
try {
archiveService.saveProject(project);
return Result.success();
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
@At("/project/onDelete")
@SLog(tag = "活动电子档案-归档项目", msg = "删除归档项目")
@SaCheckPermission("archive.project")
public Result projectOnDelete(String id) {
try {
archiveService.deleteProject(id);
return Result.success();
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
@At("/filing")
@Ok("beetl:/platform/zhgh/archive/filing/index.html")
@SaCheckPermission("archive.filing")
public void filingIndex() {
}
@At("/filing/pageData")
@SaCheckPermission("archive.filing")
public Result filingPageData(PageForm pageForm, String projectId, Integer year,
String projectStage, String docTypeId,
String documentName, Integer status) {
return Result.success(archiveService.pageDocuments(pageForm, projectId, year,
projectStage, docTypeId, documentName, status));
}
@At("/filing/fetchOne")
@SaCheckPermission("archive.filing")
public Result filingFetchOne(String id) {
return Result.success(archiveService.fetchDocument(id));
}
@At("/filing/saveDraft")
@ApiOperation("保存归档文档草稿")
@SLog(tag = "活动电子档案-资料归档", msg = "保存归档文档草稿")
@SaCheckPermission("archive.filing")
public Result filingSaveDraft(ArchiveDocument document) {
return saveDocument(document, false);
}
@At("/filing/submit")
@ApiOperation("提交归档文档")
@SLog(tag = "活动电子档案-资料归档", msg = "提交归档文档")
@SaCheckPermission("archive.filing")
public Result filingSubmit(ArchiveDocument document) {
return saveDocument(document, true);
}
@At("/filing/onDelete")
@SLog(tag = "活动电子档案-资料归档", msg = "删除归档文档")
@SaCheckPermission("archive.filing")
public Result filingOnDelete(String id) {
archiveService.deleteDocument(id);
return Result.success();
}
@At("/ledger")
@Ok("beetl:/platform/zhgh/archive/ledger/index.html")
@SaCheckPermission("archive.ledger")
public void ledgerIndex() {
}
@At("/ledger/pageData")
@SaCheckPermission("archive.ledger")
public Result ledgerPageData(PageForm pageForm, Integer year,
String projectName, String unionId, Integer status) {
return Result.success(archiveService.pageProjects(
pageForm, year, projectName, unionId, status));
}
@At("/ledger/detail")
@SaCheckPermission("archive.ledger")
public Result ledgerDetail(PageForm pageForm, String projectId,
String projectStage, String docTypeId,
String documentName, Integer status) {
return Result.success(archiveService.pageDocuments(pageForm, projectId,
null, projectStage, docTypeId, documentName, status));
}
@At("/common/stageOptions")
public Result stageOptions() {
return Result.success(ArchiveConstant.stageOptions());
}
@At("/common/unionOptions")
public Result unionOptions() {
return Result.success(archiveService.listUnions());
}
private Result saveDocument(ArchiveDocument document, boolean submit) {
try {
archiveService.saveDocument(document, submit);
return Result.success();
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
}
@@ -1,55 +0,0 @@
package com.budwk.app.zhgh.archive.model;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
/**
* 活动电子档案文档类型。
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("act_archive_doc_type")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("活动电子档案文档类型")
public class ArchiveDocType extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("主键")
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 150)
@Comment("类型名称")
private String name;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("项目阶段")
private String projectStage;
@Column
@Comment("排序编号")
private Integer sortNo;
@Column
@Comment("是否必选")
private Boolean requiredFlag;
@Column
@ColDefine(type = ColType.VARCHAR, width = 600)
@Comment("备注")
private String remark;
}
@@ -1,78 +0,0 @@
package com.budwk.app.zhgh.archive.model;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import java.util.List;
/**
* 活动电子档案归档文档。
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("act_archive_document")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("活动电子档案归档文档")
public class ArchiveDocument extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("主键")
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("项目ID")
private String projectId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 300)
@Comment("文档名称")
private String documentName;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("项目阶段")
private String projectStage;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("文档类型ID")
private String docTypeId;
@Column
@Comment("文档状态")
private Integer status;
@Column
@ColDefine(type = ColType.VARCHAR, width = 100)
@Comment("上传人")
private String uploader;
@Column
@Comment("上传时间")
private Long uploadTime;
@Column
@ColDefine(type = ColType.MYSQL_JSON)
@Comment("附件")
private List<JSONObject> files;
@Column
@ColDefine(type = ColType.VARCHAR, width = 600)
@Comment("备注")
private String remark;
}
@@ -1,55 +0,0 @@
package com.budwk.app.zhgh.archive.model;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
/**
* 活动电子档案项目。
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("act_archive_project")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("活动电子档案项目")
public class ArchiveProject extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("主键")
private String id;
@Column
@Comment("年度")
private Integer year;
@Column
@ColDefine(type = ColType.VARCHAR, width = 300)
@Comment("项目名称")
private String projectName;
@Column
@Comment("项目状态")
private Integer status;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("所属工会ID")
private String unionId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 150)
@Comment("所属工会名称")
private String unionName;
}
@@ -1,406 +0,0 @@
package com.budwk.app.zhgh.archive.service;
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.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.archive.constant.ArchiveConstant;
import com.budwk.app.zhgh.archive.model.ArchiveDocType;
import com.budwk.app.zhgh.archive.model.ArchiveDocument;
import com.budwk.app.zhgh.archive.model.ArchiveProject;
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.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 活动电子档案领域服务。
*/
@IocBean(args = {"refer:dao"})
public class ArchiveService extends BaseServiceImpl<ArchiveProject> {
public ArchiveService(Dao dao) {
super(dao);
}
public Pagination<NutMap> pageDocTypes(PageForm pageForm, String name,
String projectStage, Boolean requiredFlag) {
Cnd cnd = Cnd.NEW();
cnd.and(Cnd.likeEX("adt.name", name));
cnd.andEX("adt.projectStage", "=", projectStage);
cnd.andEX("adt.requiredFlag", "=", requiredFlag);
cnd.and("adt.delFlag", "=", false);
cnd.asc("adt.projectStage").asc("adt.sortNo").asc("adt.createdAt");
Sql sql = Sqls.create("""
SELECT adt.*,
(SELECT COUNT(1)
FROM act_archive_document ad
WHERE ad.docTypeId = adt.id
AND ad.delFlag = 0) AS documentCount
FROM act_archive_doc_type adt
$condition
""");
sql.setCondition(cnd);
Pagination<NutMap> page = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
page.getList().forEach(this::enrichDocType);
return page;
}
public List<NutMap> listDocTypes(String projectStage) {
Cnd cnd = Cnd.where("adt.delFlag", "=", false);
cnd.andEX("adt.projectStage", "=", projectStage);
cnd.asc("adt.projectStage").asc("adt.sortNo").asc("adt.createdAt");
Sql sql = Sqls.create("""
SELECT adt.*,
(SELECT COUNT(1)
FROM act_archive_document ad
WHERE ad.docTypeId = adt.id
AND ad.delFlag = 0) AS documentCount
FROM act_archive_doc_type adt
$condition
""");
sql.setCondition(cnd);
List<NutMap> list = listMap(sql);
list.forEach(this::enrichDocType);
return list;
}
@Aop(TransAop.READ_COMMITTED)
public void saveDocType(ArchiveDocType docType) {
if (docType == null || Strings.isBlank(docType.getName())) {
throw new IllegalArgumentException("类型名称不能为空");
}
if (!ArchiveConstant.validStage(docType.getProjectStage())) {
throw new IllegalArgumentException("项目阶段不正确");
}
docType.setName(docType.getName().trim());
if (docType.getSortNo() == null) {
docType.setSortNo(0);
}
if (docType.getRequiredFlag() == null) {
docType.setRequiredFlag(false);
}
Cnd duplicate = Cnd.where("projectStage", "=", docType.getProjectStage())
.and("name", "=", docType.getName())
.and("delFlag", "=", false);
if (Strings.isNotBlank(docType.getId())) {
duplicate.and("id", "<>", docType.getId());
}
if (dao().count(ArchiveDocType.class, duplicate) > 0) {
throw new IllegalArgumentException("同一项目阶段下类型名称不能重复");
}
if (Strings.isBlank(docType.getId())) {
dao().insert(docType);
} else {
ArchiveDocType old = dao().fetch(ArchiveDocType.class, docType.getId());
if (old == null) {
throw new IllegalArgumentException("文档类型不存在");
}
dao().updateIgnoreNull(docType);
}
}
@Aop(TransAop.READ_COMMITTED)
public void deleteDocType(String id) {
if (Strings.isBlank(id)) {
return;
}
if (dao().count(ArchiveDocument.class,
Cnd.where("docTypeId", "=", id).and("delFlag", "=", false)) > 0) {
throw new IllegalArgumentException("文档类型已被归档文档引用,不能删除");
}
dao().delete(ArchiveDocType.class, id);
}
public Pagination<NutMap> pageProjects(PageForm pageForm, Integer year,
String projectName, String unionId, Integer status) {
Cnd cnd = Cnd.NEW();
cnd.andEX("ap.year", "=", year);
cnd.and(Cnd.likeEX("ap.projectName", projectName));
cnd.andEX("ap.unionId", "=", unionId);
cnd.andEX("ap.status", "=", status);
cnd.and("ap.delFlag", "=", false);
cnd.desc("ap.year").desc("ap.createdAt");
Sql sql = Sqls.create("""
SELECT ap.*,
(SELECT COUNT(1)
FROM act_archive_document ad
WHERE ad.projectId = ap.id
AND ad.delFlag = 0) AS documentCount
FROM act_archive_project ap
$condition
""");
sql.setCondition(cnd);
Pagination<NutMap> page = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
page.getList().forEach(this::enrichProject);
return page;
}
public List<NutMap> listProjects() {
Cnd cnd = Cnd.where("ap.delFlag", "=", false);
cnd.desc("ap.year").desc("ap.createdAt");
Sql sql = Sqls.create("""
SELECT ap.*,
(SELECT COUNT(1)
FROM act_archive_document ad
WHERE ad.projectId = ap.id
AND ad.delFlag = 0) AS documentCount
FROM act_archive_project ap
$condition
""");
sql.setCondition(cnd);
List<NutMap> list = listMap(sql);
list.forEach(this::enrichProject);
return list;
}
@Aop(TransAop.READ_COMMITTED)
public void saveProject(ArchiveProject project) {
if (project == null || project.getYear() == null) {
throw new IllegalArgumentException("年度不能为空");
}
if (Strings.isBlank(project.getProjectName())) {
throw new IllegalArgumentException("项目名称不能为空");
}
if (Strings.isBlank(project.getUnionId())) {
throw new IllegalArgumentException("所属工会不能为空");
}
Sys_union union = dao().fetch(Sys_union.class, project.getUnionId());
if (union == null) {
throw new IllegalArgumentException("所属工会不存在");
}
project.setProjectName(project.getProjectName().trim());
project.setUnionName(union.getName());
Cnd duplicate = Cnd.where("year", "=", project.getYear())
.and("projectName", "=", project.getProjectName())
.and("unionId", "=", project.getUnionId())
.and("delFlag", "=", false);
if (Strings.isNotBlank(project.getId())) {
duplicate.and("id", "<>", project.getId());
}
if (dao().count(ArchiveProject.class, duplicate) > 0) {
throw new IllegalArgumentException("同年度同工会下项目名称不能重复");
}
if (Strings.isBlank(project.getId())) {
project.setStatus(ArchiveConstant.PROJECT_DRAFT);
dao().insert(project);
} else {
ArchiveProject old = dao().fetch(ArchiveProject.class, project.getId());
if (old == null) {
throw new IllegalArgumentException("归档项目不存在");
}
project.setStatus(old.getStatus());
dao().updateIgnoreNull(project);
}
}
@Aop(TransAop.READ_COMMITTED)
public void deleteProject(String id) {
if (Strings.isBlank(id)) {
return;
}
if (dao().count(ArchiveDocument.class,
Cnd.where("projectId", "=", id).and("delFlag", "=", false)) > 0) {
throw new IllegalArgumentException("项目下已存在归档文档,不能删除");
}
dao().delete(ArchiveProject.class, id);
}
public Pagination<NutMap> pageDocuments(PageForm pageForm, String projectId, Integer year,
String projectStage, String docTypeId,
String documentName, Integer status) {
Cnd cnd = Cnd.NEW();
cnd.andEX("ad.projectId", "=", projectId);
cnd.andEX("ap.year", "=", year);
cnd.andEX("ad.projectStage", "=", projectStage);
cnd.andEX("ad.docTypeId", "=", docTypeId);
cnd.and(Cnd.likeEX("ad.documentName", documentName));
cnd.andEX("ad.status", "=", status);
cnd.and("ad.delFlag", "=", false);
cnd.and("ap.delFlag", "=", false);
cnd.desc("ap.year").desc("ad.uploadTime").desc("ad.createdAt");
Sql sql = Sqls.create("""
SELECT ad.*, ap.year, ap.projectName, ap.unionId, ap.unionName,
adt.name AS docTypeName, adt.requiredFlag AS docTypeRequiredFlag
FROM act_archive_document ad
JOIN act_archive_project ap
ON ap.id = ad.projectId
LEFT JOIN act_archive_doc_type adt
ON adt.id = ad.docTypeId
AND adt.delFlag = 0
$condition
""");
sql.setCondition(cnd);
Pagination<NutMap> page = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
page.getList().forEach(this::enrichDocument);
return page;
}
public NutMap fetchDocument(String id) {
Sql sql = Sqls.create("""
SELECT ad.*, ap.year, ap.projectName, ap.unionId, ap.unionName,
adt.name AS docTypeName, adt.requiredFlag AS docTypeRequiredFlag
FROM act_archive_document ad
JOIN act_archive_project ap
ON ap.id = ad.projectId
LEFT JOIN act_archive_doc_type adt
ON adt.id = ad.docTypeId
AND adt.delFlag = 0
WHERE ad.id = @id
AND ad.delFlag = 0
AND ap.delFlag = 0
""");
sql.params().set("id", id);
NutMap map = fetchMap(sql);
if (map != null) {
enrichDocument(map);
}
return map;
}
@Aop(TransAop.READ_COMMITTED)
public void saveDocument(ArchiveDocument document, boolean submit) {
validateDocument(document, submit);
ArchiveDocument old = Strings.isBlank(document.getId())
? null : dao().fetch(ArchiveDocument.class, document.getId());
if (Strings.isNotBlank(document.getId()) && old == null) {
throw new IllegalArgumentException("归档文档不存在");
}
int targetStatus = submit ? ArchiveConstant.DOCUMENT_SUBMITTED : ArchiveConstant.DOCUMENT_DRAFT;
if (old != null && old.getStatus() != null
&& old.getStatus() == ArchiveConstant.DOCUMENT_SUBMITTED && !submit) {
targetStatus = ArchiveConstant.DOCUMENT_SUBMITTED;
}
document.setStatus(targetStatus);
if (targetStatus == ArchiveConstant.DOCUMENT_SUBMITTED) {
String uploader = SecurityUtil.getUserUsername();
if (Strings.isBlank(uploader)) {
uploader = SecurityUtil.getUserLoginname();
}
document.setUploader(uploader);
document.setUploadTime(System.currentTimeMillis());
} else if (old != null) {
document.setUploader(old.getUploader());
document.setUploadTime(old.getUploadTime());
}
String oldProjectId = old == null ? null : old.getProjectId();
if (old == null) {
dao().insert(document);
} else {
dao().updateIgnoreNull(document);
}
refreshProjectStatus(document.getProjectId());
if (Strings.isNotBlank(oldProjectId) && !oldProjectId.equals(document.getProjectId())) {
refreshProjectStatus(oldProjectId);
}
}
@Aop(TransAop.READ_COMMITTED)
public void deleteDocument(String id) {
ArchiveDocument document = dao().fetch(ArchiveDocument.class, id);
if (document == null) {
return;
}
dao().delete(ArchiveDocument.class, id);
refreshProjectStatus(document.getProjectId());
}
public List<Sys_union> listUnions() {
return dao().query(Sys_union.class, Cnd.where("delFlag", "=", false).asc("unionCode"));
}
private void validateDocument(ArchiveDocument document, boolean submit) {
if (document == null || Strings.isBlank(document.getProjectId())) {
throw new IllegalArgumentException("归档项目不能为空");
}
if (Strings.isBlank(document.getDocumentName())) {
throw new IllegalArgumentException("文档名称不能为空");
}
ArchiveProject project = dao().fetch(ArchiveProject.class,
Cnd.where("id", "=", document.getProjectId()).and("delFlag", "=", false));
if (project == null) {
throw new IllegalArgumentException("归档项目不存在");
}
if (!ArchiveConstant.validStage(document.getProjectStage())) {
throw new IllegalArgumentException("项目阶段不正确");
}
document.setDocumentName(document.getDocumentName().trim());
if (Strings.isNotBlank(document.getDocTypeId())) {
ArchiveDocType docType = dao().fetch(ArchiveDocType.class,
Cnd.where("id", "=", document.getDocTypeId()).and("delFlag", "=", false));
if (docType == null) {
throw new IllegalArgumentException("文档类型不存在");
}
if (!document.getProjectStage().equals(docType.getProjectStage())) {
throw new IllegalArgumentException("文档类型与项目阶段不匹配");
}
}
if (submit && (document.getFiles() == null || document.getFiles().isEmpty())) {
throw new IllegalArgumentException("提交归档前请先上传附件");
}
}
private void refreshProjectStatus(String projectId) {
if (Strings.isBlank(projectId)) {
return;
}
ArchiveProject project = dao().fetch(ArchiveProject.class, projectId);
if (project == null) {
return;
}
List<ArchiveDocument> submitted = dao().query(ArchiveDocument.class,
Cnd.where("projectId", "=", projectId)
.and("status", "=", ArchiveConstant.DOCUMENT_SUBMITTED)
.and("delFlag", "=", false));
int status = ArchiveConstant.PROJECT_DRAFT;
if (!submitted.isEmpty()) {
List<ArchiveDocType> requiredTypes = dao().query(ArchiveDocType.class,
Cnd.where("requiredFlag", "=", true).and("delFlag", "=", false));
if (requiredTypes.isEmpty()) {
status = ArchiveConstant.PROJECT_FILING;
} else {
Set<String> submittedKeys = new HashSet<>();
submitted.forEach(item -> {
if (Strings.isNotBlank(item.getDocTypeId())) {
submittedKeys.add(item.getProjectStage() + "_" + item.getDocTypeId());
}
});
boolean completed = requiredTypes.stream().allMatch(item ->
submittedKeys.contains(item.getProjectStage() + "_" + item.getId()));
status = completed ? ArchiveConstant.PROJECT_COMPLETED : ArchiveConstant.PROJECT_FILING;
}
}
dao().update(ArchiveProject.class, Chain.make("status", status),
Cnd.where("id", "=", projectId));
}
private void enrichDocType(NutMap map) {
map.put("projectStageLabel", ArchiveConstant.stageLabel(map.getString("projectStage")));
}
private void enrichProject(NutMap map) {
map.put("statusLabel", ArchiveConstant.projectStatusLabel(map.getInt("status")));
}
private void enrichDocument(NutMap map) {
map.put("projectStageLabel", ArchiveConstant.stageLabel(map.getString("projectStage")));
map.put("statusLabel", ArchiveConstant.documentStatusLabel(map.getInt("status")));
Object files = map.get("files");
if (files == null || StrUtil.isBlank(String.valueOf(files))) {
map.put("files", Collections.emptyList());
}
}
}
-192
View File
@@ -1,192 +0,0 @@
-- 活动电子档案模块初始化脚本(MySQL 8)。
-- 表结构、索引和菜单均可独立部署,不包含数据库名限定。
CREATE TABLE IF NOT EXISTS act_archive_doc_type (
id VARCHAR(32) NOT NULL COMMENT '主键',
name VARCHAR(150) NOT NULL COMMENT '类型名称',
projectStage VARCHAR(32) NOT NULL COMMENT '项目阶段:PREPARE/PROCESS/SUMMARY',
sortNo INT NOT NULL DEFAULT 0 COMMENT '排序编号',
requiredFlag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否必选',
remark VARCHAR(600) NULL COMMENT '备注',
createdBy VARCHAR(32) NULL COMMENT '创建人',
createdAt BIGINT NULL COMMENT '创建时间',
updatedBy VARCHAR(32) NULL COMMENT '修改人',
updatedAt BIGINT NULL COMMENT '修改时间',
delFlag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
UNIQUE KEY uk_archive_doc_type_stage_name (projectStage, name),
KEY idx_archive_doc_type_stage_sort (projectStage, sortNo, createdAt)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动电子档案文档类型';
CREATE TABLE IF NOT EXISTS act_archive_project (
id VARCHAR(32) NOT NULL COMMENT '主键',
year INT NOT NULL COMMENT '年度',
projectName VARCHAR(300) NOT NULL COMMENT '项目名称',
status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0草稿、1归档中、2已完成',
unionId VARCHAR(32) NOT NULL COMMENT '所属工会ID',
unionName VARCHAR(150) NOT NULL COMMENT '所属工会名称',
createdBy VARCHAR(32) NULL COMMENT '创建人',
createdAt BIGINT NULL COMMENT '创建时间',
updatedBy VARCHAR(32) NULL COMMENT '修改人',
updatedAt BIGINT NULL COMMENT '修改时间',
delFlag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
UNIQUE KEY uk_archive_project_year_name_union (year, projectName, unionId),
KEY idx_archive_project_year_created (year, createdAt),
KEY idx_archive_project_union (unionId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动电子档案项目';
CREATE TABLE IF NOT EXISTS act_archive_document (
id VARCHAR(32) NOT NULL COMMENT '主键',
projectId VARCHAR(32) NOT NULL COMMENT '归档项目ID',
documentName VARCHAR(300) NOT NULL COMMENT '文档名称',
projectStage VARCHAR(32) NOT NULL COMMENT '项目阶段:PREPARE/PROCESS/SUMMARY',
docTypeId VARCHAR(32) NULL COMMENT '文档类型ID',
status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0草稿、1已提交归档',
uploader VARCHAR(100) NULL COMMENT '上传人',
uploadTime BIGINT NULL COMMENT '上传时间',
files JSON NULL COMMENT '附件上传结果',
remark VARCHAR(600) NULL COMMENT '备注',
createdBy VARCHAR(32) NULL COMMENT '创建人',
createdAt BIGINT NULL COMMENT '创建时间',
updatedBy VARCHAR(32) NULL COMMENT '修改人',
updatedAt BIGINT NULL COMMENT '修改时间',
delFlag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
KEY idx_archive_document_query (projectId, projectStage, docTypeId, uploadTime),
KEY idx_archive_document_project_status (projectId, status),
CONSTRAINT fk_archive_document_project
FOREIGN KEY (projectId) REFERENCES act_archive_project (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动电子档案归档文档';
-- 一级菜单。
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
'9a7d8d84b14f4d6e8ab2000000000001',
'',
LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'),
'活动电子档案',
'Archive',
'menu',
'',
'',
'ti-folder',
1,
0,
'archive',
NULL,
992,
1,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
0,
'PC',
NULL,
NULL,
'h',
0,
0
FROM sys_menu
WHERE (parentId = '' OR parentId IS NULL)
AND CHAR_LENGTH(path) = 4
HAVING NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'archive') 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
'9a7d8d84b14f4d6e8ab2000000000002', p.id, CONCAT(p.path, '0001'),
'文档类型设置', 'Archive Doc Type', 'menu', '/platform/archive/docType',
'data-pjax', '', 1, 0, 'archive.docType', NULL, 1, 0, '',
UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0,
'PC', NULL, NULL, 'w', 0, 0
FROM sys_menu p
WHERE p.permission = 'archive'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'archive.docType') 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
'9a7d8d84b14f4d6e8ab2000000000003', p.id, CONCAT(p.path, '0002'),
'创建项目', 'Archive Project', 'menu', '/platform/archive/project',
'data-pjax', '', 1, 0, 'archive.project', NULL, 2, 0, '',
UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0,
'PC', NULL, NULL, 'c', 0, 0
FROM sys_menu p
WHERE p.permission = 'archive'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'archive.project') 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
'9a7d8d84b14f4d6e8ab2000000000004', p.id, CONCAT(p.path, '0003'),
'资料归档', 'Archive Filing', 'menu', '/platform/archive/filing',
'data-pjax', '', 1, 0, 'archive.filing', NULL, 3, 0, '',
UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0,
'PC', NULL, NULL, 'z', 0, 0
FROM sys_menu p
WHERE p.permission = 'archive'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'archive.filing') 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
'9a7d8d84b14f4d6e8ab2000000000005', p.id, CONCAT(p.path, '0004'),
'档案台账', 'Archive Ledger', 'menu', '/platform/archive/ledger',
'data-pjax', '', 1, 0, 'archive.ledger', NULL, 4, 0, '',
UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0,
'PC', NULL, NULL, 'd', 0, 0
FROM sys_menu p
WHERE p.permission = 'archive'
AND NOT EXISTS (
SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'archive.ledger') t
);
-- 系统管理员默认授权,其他角色可在角色管理中按需分配。
INSERT INTO sys_role_menu (roleId, menuId)
SELECT r.id, m.id
FROM sys_role r
JOIN sys_menu m ON m.permission IN (
'archive',
'archive.docType',
'archive.project',
'archive.filing',
'archive.ledger'
)
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;
@@ -1,166 +0,0 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="类型名称">
<el-input v-model="pageForm.name" clearable placeholder="请输入类型名称"
@keyup.enter.native="doSearch"></el-input>
</search-item>
<search-item label="项目阶段">
<el-select v-model="pageForm.projectStage" clearable placeholder="请选择项目阶段"
style="width: 100%" @change="doSearch">
<el-option v-for="item in stageOptions" :key="item.value"
:label="item.label" :value="item.value"></el-option>
</el-select>
</search-item>
<search-item label="是否必选">
<el-select v-model="pageForm.requiredFlag" clearable placeholder="请选择"
style="width: 100%" @change="doSearch">
<el-option label="是" :value="true"></el-option>
<el-option label="否" :value="false"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt20">
<table-tool label="文档类型设置">
<el-button type="primary" size="small" @click="openForm()">
<i class="ti-plus"></i> 新增类型
</el-button>
</table-tool>
<el-table :data="tableData" :size="tableSize">
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
<el-table-column label="项目阶段" prop="projectStageLabel" min-width="120" align="center">
<template v-slot="{row}">
<el-tag size="small">{{row.projectStageLabel}}</el-tag>
</template>
</el-table-column>
<el-table-column label="类型名称" prop="name" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column label="排序编号" prop="sortNo" width="100" align="center"></el-table-column>
<el-table-column label="是否必选" prop="requiredFlag" width="110" align="center">
<template v-slot="{row}">
<el-tag size="small" :type="row.requiredFlag ? 'danger' : 'info'">
{{row.requiredFlag ? '必选' : '非必选'}}
</el-tag>
</template>
</el-table-column>
<el-table-column label="引用文档数" prop="documentCount" width="110" align="center"></el-table-column>
<el-table-column label="备注" prop="remark" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="150" align="center">
<template v-slot="{row}">
<el-button size="mini" type="primary" @click="openForm(row)">编辑</el-button>
<el-button size="mini" type="danger" @click="onDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-dialog :title="formData.id ? '编辑文档类型' : '新增文档类型'"
:visible.sync="formVisible" width="560px" :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="150" show-word-limit></el-input>
</el-form-item>
<el-form-item label="项目阶段" prop="projectStage">
<el-select v-model="formData.projectStage" placeholder="请选择项目阶段" style="width: 100%">
<el-option v-for="item in stageOptions" :key="item.value"
:label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="排序编号" prop="sortNo">
<el-input-number v-model="formData.sortNo" :min="0" :step="1"
step-strictly controls-position="right" style="width: 100%"></el-input-number>
</el-form-item>
<el-form-item label="必选类型" prop="requiredFlag">
<el-switch v-model="formData.requiredFlag" active-text="是" inactive-text="否"></el-switch>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model.trim="formData.remark" type="textarea" :rows="3"
maxlength="600" show-word-limit></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="formVisible=false">取消</el-button>
<el-button type="primary" @click="onSubmit">保存</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
tableData: [],
stageOptions: [],
formVisible: false,
formData: {},
pageForm: {name: "", projectStage: "", requiredFlag: null},
formRules: {
name: [{required: true, message: "请输入类型名称", trigger: "blur"}],
projectStage: [{required: true, message: "请选择项目阶段", trigger: "change"}],
sortNo: [{required: true, message: "请输入排序编号", trigger: "blur"}]
}
}
},
methods: {
pageData() {
this.$axios.post("/platform/archive/docType/pageData", this.pageForm).then(res => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
}
})
},
loadStages() {
this.$axios.post("/platform/archive/common/stageOptions").then(res => {
if (res.code === 0) this.stageOptions = res.data || []
})
},
openForm(row) {
this.formData = row ? clone(row) : {sortNo: 0, requiredFlag: false}
this.formVisible = true
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
},
onSubmit() {
this.$refs.formRef.validate(async valid => {
if (!valid) return
const res = await this.$axios.post("/platform/archive/docType/onSubmit", this.formData)
if (res.code === 0) {
this.$message.success("保存成功")
this.formVisible = false
this.pageData()
} else {
this.$message.warning(res.msg)
}
})
},
onDelete(row) {
this.$confirm("确定删除文档类型“" + row.name + "”吗?", "提示", {type: "warning"})
.then(async () => {
const res = await this.$axios.post("/platform/archive/docType/onDelete", {id: row.id})
if (res.code === 0) {
this.$message.success("删除成功")
this.pageData()
} else {
this.$message.warning(res.msg)
}
}).catch(() => {})
}
},
created() {
this.loadStages()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -1,371 +0,0 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.archive-summary {display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px}
.archive-summary-title {font-size:16px; font-weight:600}
.archive-summary-meta {color:#909399; display:flex; gap:20px; flex-wrap:wrap}
.archive-file-count {cursor:pointer; color:#409eff}
</style>
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<el-date-picker v-model="pageForm.year" type="year" value-format="yyyy"
clearable placeholder="请选择年度" style="width: 100%"
@change="onYearChange"></el-date-picker>
</search-item>
<search-item label="归档项目">
<el-select v-model="pageForm.projectId" clearable filterable placeholder="请选择归档项目"
style="width: 100%" @change="onProjectChange">
<el-option v-for="item in filteredProjects" :key="item.id"
:label="item.year + ' ' + item.projectName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="项目阶段">
<el-select v-model="pageForm.projectStage" clearable placeholder="请选择项目阶段"
style="width: 100%" @change="onStageSearchChange">
<el-option v-for="item in stageOptions" :key="item.value"
:label="item.label" :value="item.value"></el-option>
</el-select>
</search-item>
<search-item label="文档类型">
<el-select v-model="pageForm.docTypeId" clearable filterable placeholder="请选择文档类型"
style="width: 100%" @change="doSearch">
<el-option v-for="item in searchDocTypeOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="文档名称">
<el-input v-model="pageForm.documentName" clearable placeholder="请输入文档名称"
@keyup.enter.native="doSearch"></el-input>
</search-item>
<search-item label="归档状态">
<el-select v-model="pageForm.status" clearable placeholder="请选择归档状态"
style="width: 100%" @change="doSearch">
<el-option label="草稿" :value="0"></el-option>
<el-option label="已提交归档" :value="1"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card v-if="currentProject" shadow="never" class="mt20">
<div class="archive-summary">
<div class="archive-summary-title">
{{currentProject.projectName}}
<el-tag size="small" :type="projectStatusType(currentProject.status)">
{{currentProject.statusLabel}}
</el-tag>
</div>
<div class="archive-summary-meta">
<span>年度:{{currentProject.year}}</span>
<span>所属工会:{{currentProject.unionName}}</span>
<span>归档文档:{{currentProject.documentCount || 0}}</span>
</div>
</div>
</el-card>
<el-card shadow="never" class="mt20">
<table-tool label="资料归档">
<el-button type="primary" size="small" :disabled="!pageForm.projectId" @click="openForm()">
<i class="ti-plus"></i> 新增归档文档
</el-button>
</table-tool>
<el-alert v-if="!projectOptions.length" title="当前没有归档项目,请先到“创建项目”中新建项目。"
type="warning" :closable="false" show-icon class="mb10"></el-alert>
<el-table :data="tableData" :size="tableSize">
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
<el-table-column label="项目阶段" prop="projectStageLabel" width="120" align="center">
<template v-slot="{row}"><el-tag size="small">{{row.projectStageLabel}}</el-tag></template>
</el-table-column>
<el-table-column label="文档名称" prop="documentName" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column label="文档类型" prop="docTypeName" min-width="150" show-overflow-tooltip>
<template v-slot="{row}">
{{row.docTypeName || "-"}}
<el-tag v-if="row.docTypeRequiredFlag" size="mini" type="danger">必选</el-tag>
</template>
</el-table-column>
<el-table-column label="上传人" prop="uploader" width="110" align="center"></el-table-column>
<el-table-column label="上传时间" prop="uploadTime" width="170" align="center">
<template v-slot="{row}">{{row.uploadTime ? $moment(row.uploadTime).format("YYYY-MM-DD HH:mm") : "-"}}</template>
</el-table-column>
<el-table-column label="附件" width="90" align="center">
<template v-slot="{row}">
<span class="archive-file-count" @click="openPreview(row)">{{fileCount(row.files)}} 个</span>
</template>
</el-table-column>
<el-table-column label="状态" prop="status" width="120" align="center">
<template v-slot="{row}">
<el-tag size="small" :type="row.status === 1 ? 'success' : 'info'">{{row.statusLabel}}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="220" align="center">
<template v-slot="{row}">
<el-button size="mini" @click="openView(row)">查看</el-button>
<el-button size="mini" type="primary" @click="openForm(row)">编辑</el-button>
<el-button size="mini" type="danger" @click="onDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-dialog :title="readonly ? '查看归档文档' : (formData.id ? '编辑归档文档' : '新增归档文档')"
:visible.sync="formVisible" width="760px" :close-on-click-modal="false">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="归档项目" prop="projectId">
<el-select v-model="formData.projectId" filterable placeholder="请选择归档项目"
style="width: 100%" :disabled="readonly">
<el-option v-for="item in projectOptions" :key="item.id"
:label="item.year + ' ' + item.projectName" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="文档名称" prop="documentName">
<el-input v-model.trim="formData.documentName" maxlength="300"
show-word-limit :disabled="readonly"></el-input>
</el-form-item>
<el-form-item label="项目阶段" prop="projectStage">
<el-select v-model="formData.projectStage" placeholder="请选择项目阶段"
style="width: 100%" :disabled="readonly" @change="onFormStageChange">
<el-option v-for="item in stageOptions" :key="item.value"
:label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="文档类型" prop="docTypeId">
<el-select v-model="formData.docTypeId" clearable filterable placeholder="请选择文档类型"
style="width: 100%" :disabled="readonly">
<el-option v-for="item in formDocTypeOptions" :key="item.id"
:label="item.name + (item.requiredFlag ? '(必选)' : '')"
:value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="上传附件" prop="files">
<file-upload v-if="!readonly" :value.sync="formData.files" :upload_number="20"
:upload_size="104857600" upload_result_type="url"
upload_result_category="array" complete_result upload_mode="drag"
accept=".doc,.docx,.pdf,.ppt,.pptx,.jpg,.jpeg,.png,.gif,.bmp,.webp,.mp4,.mov,.avi,.wmv,.mkv">
</file-upload>
<file-preview v-else :files="formData.files" complete_result></file-preview>
</el-form-item>
<el-form-item v-if="formData.id" label="上传人">
<el-input :value="formData.uploader || '-'" disabled></el-input>
</el-form-item>
<el-form-item v-if="formData.id" label="上传时间">
<el-input :value="formData.uploadTime ? $moment(formData.uploadTime).format('YYYY-MM-DD HH:mm') : '-'" disabled></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input v-model.trim="formData.remark" type="textarea" :rows="3"
maxlength="600" show-word-limit :disabled="readonly"></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="formVisible=false">关闭</el-button>
<template v-if="!readonly">
<el-button @click="saveDocument(false)">保存草稿</el-button>
<el-button type="primary" @click="saveDocument(true)">提交归档</el-button>
</template>
</span>
</el-dialog>
<el-dialog title="附件预览" :visible.sync="previewVisible" width="760px">
<file-preview :files="previewFiles" complete_result></file-preview>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
tableData: [],
stageOptions: [],
projectOptions: [],
searchDocTypeOptions: [],
formDocTypeOptions: [],
currentProject: null,
formVisible: false,
previewVisible: false,
readonly: false,
previewFiles: [],
formData: {},
pageForm: {
year: String(new Date().getFullYear()),
projectId: "",
projectStage: "",
docTypeId: "",
documentName: "",
status: null
},
formRules: {
projectId: [{required: true, message: "请选择归档项目", trigger: "change"}],
documentName: [{required: true, message: "请输入文档名称", trigger: "blur"}],
projectStage: [{required: true, message: "请选择项目阶段", trigger: "change"}]
}
}
},
computed: {
filteredProjects() {
if (!this.pageForm.year) return this.projectOptions
return this.projectOptions.filter(item => String(item.year) === String(this.pageForm.year))
}
},
methods: {
normalizeFiles(files) {
if (Array.isArray(files)) return files
if (!files) return []
try { return JSON.parse(files) || [] } catch (e) { return [] }
},
fileCount(files) {
return this.normalizeFiles(files).length
},
projectStatusType(status) {
return status === 2 ? "success" : (status === 1 ? "warning" : "info")
},
pageData() {
if (!this.pageForm.projectId) {
this.tableData = []
this.pageForm.totalCount = 0
return
}
this.$axios.post("/platform/archive/filing/pageData", this.pageForm).then(res => {
if (res.code === 0) {
this.tableData = (res.data.list || []).map(item =>
Object.assign({}, item, {files: this.normalizeFiles(item.files)}))
this.pageForm.totalCount = res.data.totalCount
}
})
},
async loadBaseData() {
const [stages, projects] = await Promise.all([
this.$axios.post("/platform/archive/common/stageOptions"),
this.$axios.post("/platform/archive/project/listData")
])
this.stageOptions = stages.data || []
this.projectOptions = projects.data || []
const queryProjectId = new URLSearchParams(window.location.search).get("projectId")
if (queryProjectId && this.projectOptions.some(item => item.id === queryProjectId)) {
this.pageForm.projectId = queryProjectId
const project = this.projectOptions.find(item => item.id === queryProjectId)
this.pageForm.year = String(project.year)
} else if (this.filteredProjects.length) {
this.pageForm.projectId = this.filteredProjects[0].id
} else if (this.projectOptions.length) {
this.pageForm.projectId = this.projectOptions[0].id
this.pageForm.year = String(this.projectOptions[0].year)
}
this.syncProject()
this.pageData()
},
loadDocTypes(stage, target) {
if (!stage) {
this[target] = []
return Promise.resolve()
}
return this.$axios.post("/platform/archive/docType/listData", {projectStage: stage}).then(res => {
this[target] = res.data || []
})
},
syncProject() {
this.currentProject = this.projectOptions.find(item => item.id === this.pageForm.projectId) || null
},
onYearChange() {
if (!this.filteredProjects.some(item => item.id === this.pageForm.projectId)) {
this.pageForm.projectId = this.filteredProjects.length ? this.filteredProjects[0].id : ""
}
this.onProjectChange()
},
onProjectChange() {
this.syncProject()
this.doSearch()
},
async onStageSearchChange() {
this.pageForm.docTypeId = ""
await this.loadDocTypes(this.pageForm.projectStage, "searchDocTypeOptions")
this.doSearch()
},
async onFormStageChange() {
this.formData.docTypeId = ""
await this.loadDocTypes(this.formData.projectStage, "formDocTypeOptions")
},
async openForm(row) {
this.readonly = false
this.formData = row ? Object.assign({}, clone(row), {files: this.normalizeFiles(row.files)}) : {
projectId: this.pageForm.projectId,
documentName: "",
projectStage: "",
docTypeId: "",
files: [],
remark: ""
}
await this.loadDocTypes(this.formData.projectStage, "formDocTypeOptions")
this.formVisible = true
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
},
async openView(row) {
this.readonly = true
this.formData = Object.assign({}, clone(row), {files: this.normalizeFiles(row.files)})
await this.loadDocTypes(this.formData.projectStage, "formDocTypeOptions")
this.formVisible = true
},
openPreview(row) {
this.previewFiles = this.normalizeFiles(row.files)
if (!this.previewFiles.length) {
this.$message.warning("当前文档没有附件")
return
}
this.previewVisible = true
},
saveDocument(submit) {
this.$refs.formRef.validate(async valid => {
if (!valid) return
if (submit && !this.normalizeFiles(this.formData.files).length) {
this.$message.warning("提交归档前请先上传附件")
return
}
const data = Object.assign({}, this.formData, {
files: JSON.stringify(this.normalizeFiles(this.formData.files))
})
const url = submit ? "/platform/archive/filing/submit" : "/platform/archive/filing/saveDraft"
const res = await this.$axios.post(url, data)
if (res.code === 0) {
this.$message.success(submit ? "提交归档成功" : "草稿保存成功")
this.formVisible = false
await this.reloadProjects()
this.pageData()
} else {
this.$message.warning(res.msg)
}
})
},
onDelete(row) {
this.$confirm("确定删除归档文档“" + row.documentName + "”吗?", "提示", {type: "warning"})
.then(async () => {
const res = await this.$axios.post("/platform/archive/filing/onDelete", {id: row.id})
if (res.code === 0) {
this.$message.success("删除成功")
await this.reloadProjects()
this.pageData()
} else {
this.$message.warning(res.msg)
}
}).catch(() => {})
},
async reloadProjects() {
const res = await this.$axios.post("/platform/archive/project/listData")
this.projectOptions = res.data || []
this.syncProject()
}
},
created() {
this.loadBaseData()
}
})
</script>
<!--#
}
#-->
@@ -1,186 +0,0 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.archive-ledger-header {margin-bottom:16px}
.archive-ledger-title {font-size:17px; font-weight:600; margin-bottom:10px}
.archive-ledger-meta {display:flex; gap:20px; flex-wrap:wrap; color:#909399}
</style>
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<el-date-picker v-model="pageForm.year" type="year" value-format="yyyy"
clearable placeholder="请选择年度" style="width: 100%"
@change="doSearch"></el-date-picker>
</search-item>
<search-item label="项目名称">
<el-input v-model="pageForm.projectName" clearable placeholder="请输入项目名称"
@keyup.enter.native="doSearch"></el-input>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会"
style="width: 100%" @change="doSearch">
<el-option v-for="item in unionOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt20">
<table-tool label="档案台账"></table-tool>
<el-table :data="tableData" :size="tableSize">
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
<el-table-column label="年度" prop="year" width="90" align="center"></el-table-column>
<el-table-column label="项目名称" prop="projectName" min-width="210" show-overflow-tooltip></el-table-column>
<el-table-column label="项目状态" width="110" align="center">
<template v-slot="{row}">
<el-tag size="small" :type="projectStatusType(row.status)">{{row.statusLabel}}</el-tag>
</template>
</el-table-column>
<el-table-column label="所属工会" prop="unionName" min-width="170" show-overflow-tooltip></el-table-column>
<el-table-column label="归档文档数" prop="documentCount" width="120" align="center"></el-table-column>
<el-table-column label="创建时间" prop="createdAt" width="170" align="center">
<template v-slot="{row}">{{$moment(row.createdAt).format("YYYY-MM-DD HH:mm")}}</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template v-slot="{row}">
<el-button size="mini" type="primary" @click="openLedger(row)">查阅</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-drawer title="项目归档台账" :visible.sync="drawerVisible" size="82%" :destroy-on-close="true">
<div v-if="currentProject" style="padding:0 20px 20px">
<div class="archive-ledger-header">
<div class="archive-ledger-title">
{{currentProject.projectName}}
<el-tag size="small" :type="projectStatusType(currentProject.status)">
{{currentProject.statusLabel}}
</el-tag>
</div>
<div class="archive-ledger-meta">
<span>年度:{{currentProject.year}}</span>
<span>所属工会:{{currentProject.unionName}}</span>
<span>归档文档:{{currentProject.documentCount || 0}}</span>
</div>
</div>
<el-table :data="detailData" size="small">
<el-table-column label="序号" type="index" width="60"></el-table-column>
<el-table-column label="项目阶段" prop="projectStageLabel" width="120"></el-table-column>
<el-table-column label="文档名称" prop="documentName" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column label="文档类型" prop="docTypeName" min-width="140" show-overflow-tooltip></el-table-column>
<el-table-column label="上传人" prop="uploader" width="110"></el-table-column>
<el-table-column label="上传时间" width="160">
<template v-slot="{row}">{{row.uploadTime ? $moment(row.uploadTime).format("YYYY-MM-DD HH:mm") : "-"}}</template>
</el-table-column>
<el-table-column label="状态" width="120" align="center">
<template v-slot="{row}">
<el-tag size="small" :type="row.status === 1 ? 'success' : 'info'">{{row.statusLabel}}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center">
<template v-slot="{row}">
<el-button size="mini" type="primary" @click="preview(row)">查看附件</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination style="margin-top:15px;text-align:center"
:current-page="detailForm.pageNumber"
:page-size="detailForm.pageSize"
:page-sizes="[10,20,50]"
:total="detailForm.totalCount"
layout="total, sizes, prev, pager, next"
@current-change="val=>{detailForm.pageNumber=val;loadDetail()}"
@size-change="val=>{detailForm.pageSize=val;detailForm.pageNumber=1;loadDetail()}">
</el-pagination>
</div>
</el-drawer>
<el-dialog title="附件预览" :visible.sync="previewVisible" width="760px">
<file-preview :files="previewFiles" complete_result></file-preview>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
tableData: [],
detailData: [],
unionOptions: [],
drawerVisible: false,
previewVisible: false,
previewFiles: [],
currentProject: null,
pageForm: {
year: String(new Date().getFullYear()),
projectName: "",
unionId: ""
},
detailForm: {pageNumber: 1, pageSize: 10, totalCount: 0, projectId: ""}
}
},
methods: {
normalizeFiles(files) {
if (Array.isArray(files)) return files
if (!files) return []
try { return JSON.parse(files) || [] } catch (e) { return [] }
},
projectStatusType(status) {
return status === 2 ? "success" : (status === 1 ? "warning" : "info")
},
pageData() {
this.$axios.post("/platform/archive/ledger/pageData", this.pageForm).then(res => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
}
})
},
loadUnions() {
this.$axios.post("/platform/archive/common/unionOptions").then(res => {
if (res.code === 0) this.unionOptions = res.data || []
})
},
openLedger(row) {
this.currentProject = row
this.detailForm = {pageNumber: 1, pageSize: 10, totalCount: 0, projectId: row.id}
this.drawerVisible = true
this.loadDetail()
},
loadDetail() {
this.$axios.post("/platform/archive/ledger/detail", this.detailForm).then(res => {
if (res.code === 0) {
this.detailData = (res.data.list || []).map(item =>
Object.assign({}, item, {files: this.normalizeFiles(item.files)}))
this.detailForm.totalCount = res.data.totalCount
}
})
},
preview(row) {
this.previewFiles = this.normalizeFiles(row.files)
if (!this.previewFiles.length) {
this.$message.warning("当前文档没有附件")
return
}
this.previewVisible = true
}
},
created() {
this.loadUnions()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -1,183 +0,0 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<el-date-picker v-model="pageForm.year" type="year" value-format="yyyy"
clearable placeholder="请选择年度" style="width: 100%"
@change="doSearch"></el-date-picker>
</search-item>
<search-item label="项目名称">
<el-input v-model="pageForm.projectName" clearable placeholder="请输入项目名称"
@keyup.enter.native="doSearch"></el-input>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会"
style="width: 100%" @change="doSearch">
<el-option v-for="item in unionOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="项目状态">
<el-select v-model="pageForm.status" clearable placeholder="请选择项目状态"
style="width: 100%" @change="doSearch">
<el-option label="草稿" :value="0"></el-option>
<el-option label="归档中" :value="1"></el-option>
<el-option label="已完成" :value="2"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt20">
<table-tool label="归档项目">
<el-button type="primary" size="small" @click="openForm()">
<i class="ti-plus"></i> 新建项目
</el-button>
</table-tool>
<el-table :data="tableData" :size="tableSize">
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
<el-table-column label="年度" prop="year" width="90" align="center"></el-table-column>
<el-table-column label="项目名称" prop="projectName" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column label="项目状态" prop="status" width="110" align="center">
<template v-slot="{row}">
<el-tag size="small" :type="statusType(row.status)">{{row.statusLabel}}</el-tag>
</template>
</el-table-column>
<el-table-column label="所属工会" prop="unionName" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column label="归档文档数" prop="documentCount" width="110" align="center"></el-table-column>
<el-table-column label="创建时间" prop="createdAt" width="170" align="center">
<template v-slot="{row}">{{$moment(row.createdAt).format("YYYY-MM-DD HH:mm")}}</template>
</el-table-column>
<el-table-column label="操作" width="240" align="center">
<template v-slot="{row}">
<el-button size="mini" type="primary" @click="openForm(row)">编辑</el-button>
<el-button size="mini" type="success" @click="goFiling(row)">资料归档</el-button>
<el-button size="mini" type="danger" @click="onDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-dialog :title="formData.id ? '编辑归档项目' : '新建归档项目'"
:visible.sync="formVisible" width="560px" :close-on-click-modal="false">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="年度" prop="year">
<el-date-picker v-model="formData.year" type="year" value-format="yyyy"
placeholder="请选择年度" style="width: 100%"></el-date-picker>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model.trim="formData.projectName" maxlength="300" show-word-limit></el-input>
</el-form-item>
<el-form-item label="所属工会" prop="unionId">
<el-select v-model="formData.unionId" filterable placeholder="请选择所属工会"
style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item v-if="formData.id" label="项目状态">
<el-input :value="formData.statusLabel || '草稿'" disabled></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="formVisible=false">取消</el-button>
<el-button type="primary" @click="onSubmit">保存</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
tableData: [],
unionOptions: [],
formVisible: false,
formData: {},
pageForm: {
year: String(new Date().getFullYear()),
projectName: "",
unionId: "",
status: null
},
formRules: {
year: [{required: true, message: "请选择年度", trigger: "change"}],
projectName: [{required: true, message: "请输入项目名称", trigger: "blur"}],
unionId: [{required: true, message: "请选择所属工会", trigger: "change"}]
}
}
},
methods: {
statusType(status) {
return status === 2 ? "success" : (status === 1 ? "warning" : "info")
},
pageData() {
this.$axios.post("/platform/archive/project/pageData", this.pageForm).then(res => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
}
})
},
loadUnions() {
this.$axios.post("/platform/archive/common/unionOptions").then(res => {
if (res.code === 0) this.unionOptions = res.data || []
})
},
openForm(row) {
this.formData = row ? clone(row) : {
year: String(new Date().getFullYear()),
projectName: "",
unionId: ""
}
if (this.formData.year != null) this.formData.year = String(this.formData.year)
this.formVisible = true
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
},
onSubmit() {
this.$refs.formRef.validate(async valid => {
if (!valid) return
const data = Object.assign({}, this.formData, {year: Number(this.formData.year)})
const res = await this.$axios.post("/platform/archive/project/onSubmit", data)
if (res.code === 0) {
this.$message.success("保存成功")
this.formVisible = false
this.pageData()
} else {
this.$message.warning(res.msg)
}
})
},
onDelete(row) {
this.$confirm("确定删除归档项目“" + row.projectName + "”吗?", "提示", {type: "warning"})
.then(async () => {
const res = await this.$axios.post("/platform/archive/project/onDelete", {id: row.id})
if (res.code === 0) {
this.$message.success("删除成功")
this.pageData()
} else {
this.$message.warning(res.msg)
}
}).catch(() => {})
},
goFiling(row) {
window.location.href = "/platform/archive/filing?projectId=" + encodeURIComponent(row.id)
}
},
created() {
this.loadUnions()
this.pageData()
}
})
</script>
<!--#
}
#-->