16 Commits
Author SHA1 Message Date
c-hongqw1 3db3de39c5 commit 2026-11-06 17:33:41 +08:00
c-hongqw1 9bb29a57de Merge remote-tracking branch 'origin/feature_会员查询' into release_20261106 2026-11-06 17:26:16 +08:00
c-hongqw1 49407374b0 慰问、会员入会接入待办 2026-11-06 17:25:12 +08:00
c-chenxs1 ac3e08fe5d feat: 优化品牌活动报名失败 2026-11-05 21:07:04 +08:00
c-chengc1 f83513d8f1 feat:移动端整改 增加会员查询页面 2026-11-05 20:58:18 +08:00
c-chenxs1 f4bbb379c3 feat: 清除.idea 2026-11-05 20:42:31 +08:00
c-chenxs1 a01ebd7d3e feat: 清除target 2026-11-05 20:40:25 +08:00
c-duxc1 e744b7c271 南京理工-分级核对:工会组织管理页面仅给‘机关党委工会’的成员单位列表,三级单位管理员设置按钮不显示问题修复; 2026-10-27 20:45:32 +08:00
c-duxc1 eaac21eebc 南京理工-分级核对需求:
1.工会组织管理页面仅给‘机关党委工会’的成员单位列表加三级单位管理员设置按钮;
2.新增‘分级核对管理’(校工会管理员可见)和‘单位数据核对’(三级单位管理员和分工会管理员可见)菜单页面;
2026-10-14 16:26:16 +08:00
c-duxc1 f9a87d12e2 南京理工查询统计(数据看板)优化整改: 会员管理和医疗互助统计总数加点击详情弹出人员列表; 2026-10-10 10:26:46 +08:00
c-hongqw1 d551f8c94e 体育报名外援优化;体育服装管理整改,关联选择活动;比赛名称录入可选工会补全;沿用保存活动bug修复 2026-10-09 10:59:09 +08:00
c-duxc1 d6961ebf19 南京理工提案优化整改: 提案查询和导出新增字段,导出前确认搜索条件; 2026-10-09 09:41:24 +08:00
c-hongqw1 b2fc1979fe 体育活动创建可以沿用,承办单位选填;导出出现重复人员修复;优化增加外援功能,也可以导出;报名界面的设置领导以及教练不能删除修复 2026-09-28 17:43:46 +08:00
c-zhangr1 39bdcbfc14 commit 2026-09-23 20:28:31 +08:00
c-duxc1 693848bb16 南理工体育活动-体育比赛活动-活动信息管理:导出活动人员名单新增字段(导出信息需要新增报名人是否会员、是否在职在编、是何人填报的报名信息以及报名人电话号码。并且导出数据需要按照所属学院编号进行排序,对于缺乏必要信息的数据如身份证号,电话,工号等需要进行警示); 2026-09-23 19:47:52 +08:00
c-duxc1 4b110407c6 南理工体育活动-创建联合工会菜单-设置管理员:新增‘体育活动联合工会管理员’角色(代替原来的工会管理员角色H04); 2026-09-23 17:29:56 +08:00
3345 changed files with 7356 additions and 550392 deletions
+28
View File
@@ -0,0 +1,28 @@
# Maven build output
target/
*.class
# IDE settings
.idea/
*.iml
*.ipr
*.iws
.vscode/
.classpath
.project
.settings/
target
# Logs and temporary files
*.log
/logs/
/temp/
/tmp/
*.tmp
*.swp
*.swo
*~
# Operating system files
.DS_Store
Thumbs.db
Desktop.ini
-14
View File
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile default="true" name="Default" enabled="true" />
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="mini" />
</profile>
</annotationProcessing>
</component>
</project>
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>
-30
View File
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="nutz-snapshots" />
<option name="name" value="nutz-snapshots" />
<option name="url" value="https://maven.aliyun.com/repository/public" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://maven.aliyun.com/repository/public" />
</remote-repository>
<remote-repository>
<option name="id" value="nutz" />
<option name="name" value="nutz" />
<option name="url" value="https://maven.aliyun.com/repository/public" />
</remote-repository>
</component>
</project>
-25
View File
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_15" default="true" project-jdk-name="15" project-jdk-type="JavaSDK" />
<component name="SvnBranchConfigurationManager">
<option name="myConfigurationMap">
<map>
<entry key="$PROJECT_DIR$">
<value>
<SvnBranchConfiguration>
<option name="trunkUrl" value="http://28bq156916.qicp.vip/svn/UNION_NJUST" />
</SvnBranchConfiguration>
</value>
</entry>
</map>
</option>
</component>
</project>
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="svn" />
</component>
</project>
-231
View File
@@ -1,231 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="0fb07392-d0a7-4efe-b746-d08bbfef26ce" name="Changes" comment="广播订阅">
<change beforePath="$PROJECT_DIR$/src/main/java/io/v/nutz/web/commons/shiro/config/EasyCredentialsMatch.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/io/v/nutz/web/commons/shiro/config/EasyCredentialsMatch.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/io/v/nutz/web/controllers/mobile/main/MobileMainCon.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/io/v/nutz/web/controllers/mobile/main/MobileMainCon.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/io/v/nutz/web/controllers/platform/sys/SysHomeController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/io/v/nutz/web/controllers/platform/sys/SysHomeController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/test/java/io/v/nutz/WebTestDemo.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/test/java/io/v/nutz/WebTestDemo.java" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Class" />
</list>
</option>
</component>
<component name="GitSEFilterConfiguration">
<file-type-list>
<filtered-out-file-type name="LOCAL_BRANCH" />
<filtered-out-file-type name="REMOTE_BRANCH" />
<filtered-out-file-type name="TAG" />
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
</file-type-list>
</component>
<component name="MavenImportPreferences">
<option name="generalSettings">
<MavenGeneralSettings>
<option name="localRepository" value="E:\apps\maven\maven_repo" />
<option name="mavenHome" value="$PROJECT_DIR$/../../apps/maven/apache-maven-3.6.3" />
<option name="userSettingsFile" value="E:\apps\maven\apache-maven-3.6.3\conf\settings.xml" />
</MavenGeneralSettings>
</option>
</component>
<component name="ProjectId" id="2zOwLE5J5wsHdx6LOp9U6VCtlFG" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="project.structure.last.edited" value="Project" />
<property name="project.structure.proportion" value="0.0" />
<property name="project.structure.side.proportion" value="0.2" />
<property name="settings.editor.selected.configurable" value="vcs.Subversion" />
<property name="vue.rearranger.settings.migration" value="true" />
</component>
<component name="RunManager" selected="Application.MainLauncher">
<configuration name="MainLauncher" type="Application" factoryName="Application" temporary="true" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="io.v.nutz.MainLauncher" />
<module name="mini" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="io.v.nutz.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration name="WebTestDemo.addAidMember" type="JUnit" factoryName="JUnit" temporary="true" nameIsGenerated="true">
<module name="mini" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="io.v.nutz.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="io.v.nutz" />
<option name="MAIN_CLASS_NAME" value="io.v.nutz.WebTestDemo" />
<option name="METHOD_NAME" value="addAidMember" />
<option name="TEST_OBJECT" value="method" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration name="WebTestDemo.updateTask" type="JUnit" factoryName="JUnit" temporary="true" nameIsGenerated="true">
<module name="mini" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="io.v.nutz.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="io.v.nutz" />
<option name="MAIN_CLASS_NAME" value="io.v.nutz.WebTestDemo" />
<option name="METHOD_NAME" value="updateTask" />
<option name="TEST_OBJECT" value="method" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<recent_temporary>
<list>
<item itemvalue="Application.MainLauncher" />
<item itemvalue="JUnit.WebTestDemo.updateTask" />
<item itemvalue="JUnit.WebTestDemo.addAidMember" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="SvnConfiguration" cleanupOnStartRun="true">
<configuration>C:\Users\Administrator\AppData\Roaming\Subversion</configuration>
<supportedVersion>125</supportedVersion>
</component>
<component name="SvnFileUrlMappingImpl">
<option name="myMappingRoots">
<list>
<SvnCopyRootSimple>
<option name="myVcsRoot" value="E:\project\UNION_NJUST" />
<option name="myCopyRoot" value="E:\project\UNION_NJUST" />
</SvnCopyRootSimple>
</list>
</option>
<option name="myMoreRealMappingRoots">
<list>
<SvnCopyRootSimple>
<option name="myVcsRoot" value="E:\project\UNION_NJUST" />
<option name="myCopyRoot" value="E:\project\UNION_NJUST" />
</SvnCopyRootSimple>
</list>
</option>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="0fb07392-d0a7-4efe-b746-d08bbfef26ce" name="Changes" comment="" />
<created>1751615372556</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1751615372556</updated>
<workItem from="1751615377650" duration="57693000" />
</task>
<task id="LOCAL-00001">
<created>1751963115714</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1751963115714</updated>
</task>
<task id="LOCAL-00002">
<created>1753181241498</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1753181241498</updated>
</task>
<task id="LOCAL-00003">
<created>1754897236551</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1754897236551</updated>
</task>
<task id="LOCAL-00004">
<created>1754964481025</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1754964481025</updated>
</task>
<task id="LOCAL-00005">
<created>1755138327488</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1755138327488</updated>
</task>
<task id="LOCAL-00006" summary="广播订阅">
<created>1755155438531</created>
<option name="number" value="00006" />
<option name="presentableId" value="LOCAL-00006" />
<option name="project" value="LOCAL" />
<updated>1755155438531</updated>
</task>
<task id="LOCAL-00007" summary="广播订阅">
<created>1764121969148</created>
<option name="number" value="00007" />
<option name="presentableId" value="LOCAL-00007" />
<option name="project" value="LOCAL" />
<updated>1764121969148</updated>
</task>
<task id="LOCAL-00008" summary="广播订阅">
<created>1774437124422</created>
<option name="number" value="00008" />
<option name="presentableId" value="LOCAL-00008" />
<option name="project" value="LOCAL" />
<updated>1774437124422</updated>
</task>
<task id="LOCAL-00009" summary="广播订阅">
<created>1776763699110</created>
<option name="number" value="00009" />
<option name="presentableId" value="LOCAL-00009" />
<option name="project" value="LOCAL" />
<updated>1776763699110</updated>
</task>
<task id="LOCAL-00010" summary="广播订阅">
<created>1776823685058</created>
<option name="number" value="00010" />
<option name="presentableId" value="LOCAL-00010" />
<option name="project" value="LOCAL" />
<updated>1776823685058</updated>
</task>
<task id="LOCAL-00011" summary="广播订阅">
<created>1778468883862</created>
<option name="number" value="00011" />
<option name="presentableId" value="LOCAL-00011" />
<option name="project" value="LOCAL" />
<updated>1778468883862</updated>
</task>
<option name="localTasksCounter" value="12" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="广播订阅" />
<option name="LAST_COMMIT_MESSAGE" value="广播订阅" />
</component>
</project>
@@ -3,8 +3,8 @@ package io.v.nutz.activity.controller.applets;
import io.v.nutz.activity.models.ActivitySchoolApply;
import io.v.nutz.activity.services.ActivitySchoolApplyService;
import io.v.nutz.activity.services.ActivitySchoolService;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.utils.Roles;
import io.v.nutz.dao.CndPlus;
import io.v.nutz.modules.models.PageForm;
import io.v.nutz.service.ViService;
@@ -158,14 +158,17 @@ public class ActivityListController {
Cnd cnd = Cnd.NEW();
Sql sql = Sqls.create("""
SELECT id applyUser, loginname, username, sex, mobile, unitid unitId, unionid unionId FROM `user` $condition
""").setParam("userId", userId).setParam("roleId", Roles.UNION_MANGER);
SELECT u.id applyUser, u.loginname, u.username, u.sex, u.mobile, u.unitid unitId, un.unionid unionId
FROM `user` u
LEFT JOIN activity_basic_unit un ON un.id = u.unitid
$condition
""");
cnd.and("unionid", "in", "(SELECT sur.unionid FROM sys_user_role sur WHERE sur.userId = '" + userId + "' AND sur.roleId = '" + Roles.UNION_MANGER + "')");
cnd.and("un.unionid", "in", ActivityRoleUtil.getManageUnionStr(userId));
if (Strings.isNotBlank(searchName)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.andLike("username", searchName).orLike("loginname", searchName);
group.andLike("u.username", searchName).orLike("u.loginname", searchName);
cnd.and(group);
}
sql.setCondition(cnd);
@@ -10,11 +10,11 @@ import cn.hutool.core.util.StrUtil;
import cn.wizzer.framework.base.Result;
import io.v.nutz.activity.models.ActivityUserCnd;
import io.v.nutz.activity.models.ActivityUserScope;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.activity.services.ActivityBasicScopeService;
import io.v.nutz.activity.template.UserTemp;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.service.SimpleService;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.modules.models.PageForm;
import io.v.nutz.sys.models.Sys_role;
import io.v.nutz.util.ViTool;
@@ -255,7 +255,7 @@ public class ActivityBasicScopeController {
""");
Cnd cnd = Cnd.NEW();
if (!ShiroUtil.hasAnyRoles("sysadmin,H06,H10")) {
if (ShiroUtil.hasAnyRoles(new String[]{"H04", "club01"})) {
if (ActivityRoleUtil.isUnionManager() || ShiroUtil.hasRole("club01")) {
cnd.and("creator", "=", ShiroUtil.getPrincipalProperty("id"));
}
}
@@ -300,11 +300,11 @@ public class ActivityBasicScopeController {
return new NutMap() {{
addv("is_H10", ShiroUtil.hasRole("H10"));
addv("is_A06", ShiroUtil.hasRole("A06"));
addv("is_H04", ShiroUtil.hasRole("H04"));
addv("is_H04", ActivityRoleUtil.isUnionManager());
addv("is_H02", ShiroUtil.hasRole("club01"));
addv("is_H03", ShiroUtil.hasRole("H03"));
addv("is_sysadmin", ShiroUtil.hasRole("sysadmin"));
addv("unionid", Vi.getUnionId());
addv("unionid", ActivityRoleUtil.getCurrentUnionId(simpleService.dao()));
}};
}
@@ -186,14 +186,15 @@ public class ActivityUnionController {
"\tgh.*,\n" +
"\tun.isEnable AS unisEnable,\n" +
"\t(SELECT COUNT(*) FROM activity_basic_unit WHERE unionid=gh.id) unitnum,\n" +
"\tGROUP_CONCAT( fzr.username,'(',fzr.loginname,')' ) fzr\n" +
"\tGROUP_CONCAT(DISTINCT CONCAT(fzr.username,'(',fzr.loginname,')')) fzr\n" +
"FROM\n" +
"\tactivity_basic_union gh\n" +
"\tLEFT JOIN sys_union un ON un.id = gh.id\n" +
"\tLEFT JOIN sys_user_role sur ON gh.id = sur.unionid \n" +
"\tAND (sur.roleId = @ghfzrrole)\n" +
"\tAND sur.roleId IN (@ghfzrrole, @activityUnionAdminRole)\n" +
"\tLEFT JOIN sys_user fzr ON sur.userId = fzr.id $condition");
sql.setParam("ghfzrrole", Roles.UNION_MANGER);
sql.setParam("activityUnionAdminRole", Roles.SPORTS_ACTIVITY_UNION_ADMIN);
Cnd cnd = Cnd.NEW();
cnd.groupBy("gh.id");
if (Strings.isNotBlank(searchName) && Strings.isNotBlank(searchKeyword)) {
@@ -309,8 +310,10 @@ public class ActivityUnionController {
sysUnionService.update("activity_basic_unit", Chain.make("unionid", ""), Cnd.where("unionid", "=", id));
//再删除工会负责人的权限
// sysUnionService.clear("sys_user_role", Cnd.where("unionid", "=", id).and("roleid", "=", Roles.UNION_MANGER));
//只清理当前联合工会的体育活动管理员,不影响H04和其他联合工会关系
sysUnionService.clear("sys_user_role", Cnd.where("unionid", "=", id).and("roleid", "=", Roles.SPORTS_ACTIVITY_UNION_ADMIN));
sysUserService.clearCache();
sysRoleService.clearCache();
// flushDict();
@@ -332,7 +335,7 @@ public class ActivityUnionController {
public Object getUserAndFzr(String id) {
try {
Sql sql = Sqls.create("SELECT u.id,u.username,u.loginname,CONCAT( u.username, '(', u.loginname, ')','-',u.unitname ) name from `user` u left join activity_basic_unit un on u.unitid=un.id WHERE un.unionid = @id").setParam("id", id);
Sql sql1 = Sqls.create("SELECT u.id,u.username,u.loginname,CONCAT( u.username, '(', u.loginname, ')','-',u.unitname ) name from `user` u left join activity_basic_unit un on u.unitid=un.id left join sys_user_role sur on u.id = sur.userId WHERE sur.unionid = @id and sur.userId is not null and sur.roleId = @roleid").setParam("id", id).setParam("roleid", Roles.UNION_MANGER);
Sql sql1 = Sqls.create("SELECT u.id,u.username,u.loginname,CONCAT( u.username, '(', u.loginname, ')','-',u.unitname ) name from `user` u left join activity_basic_unit un on u.unitid=un.id left join sys_user_role sur on u.id = sur.userId WHERE sur.unionid = @id and sur.userId is not null and sur.roleId = @roleid").setParam("id", id).setParam("roleid", Roles.SPORTS_ACTIVITY_UNION_ADMIN);
return Result.success().addData(Record.create().set("data", sysUnionService.list(sql)).set("value", sysUnionService.list(sql1)));
} catch (Exception e) {
return Result.error();
@@ -349,10 +352,10 @@ public class ActivityUnionController {
@At
public Object doEditFzr(String unionid, String userids) {
try {
//再删除工会负责人的权限
sysUnionService.clear("sys_user_role", Cnd.where("unionid", "=", unionid).and("roleid", "=", Roles.UNION_MANGER));
//只维护体育活动联合工会管理员,不修改原H04关系
sysUnionService.clear("sys_user_role", Cnd.where("unionid", "=", unionid).and("roleid", "=", Roles.SPORTS_ACTIVITY_UNION_ADMIN));
for (String id : Json.fromJsonAsList(String.class, userids)) {
sysUnionService.insert("sys_user_role", Chain.make("roleid", Roles.UNION_MANGER).add("userid", id).add("unionid", unionid));
sysUnionService.insert("sys_user_role", Chain.make("roleid", Roles.SPORTS_ACTIVITY_UNION_ADMIN).add("userid", id).add("unionid", unionid));
}
sysUserService.clearCache();
sysRoleService.clearCache();
@@ -6,6 +6,7 @@ import io.v.nutz.activity.models.*;
import io.v.nutz.activity.services.ActivitySchoolApplyService;
import io.v.nutz.activity.services.ActivitySchoolService;
import io.v.nutz.activity.services.ActivitySchoolTeamService;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.service.SimpleService;
import io.v.nutz.base.utils.DateUtil;
@@ -88,7 +89,11 @@ public class ActivityApplyController {
LEFT JOIN activity_school acts ON acts.id=apply.activityId
LEFT JOIN activity_event ev ON ev.id = apply.eventId $condition
""");
cnd.and("apply.activityUnionId", "=", getUnionId());
// 其他工会出战的外援仍由原报名人维护,重新打开项目时必须回显。
SqlExpressionGroup ownerOrUnion = new SqlExpressionGroup();
ownerOrUnion.or("apply.activityUnionId", "=", getUnionId());
ownerOrUnion.or("apply.applyUser", "=", ShiroUtil.getUserId());
cnd.and(ownerOrUnion);
/* if (!ShiroUtil.hasAnyRoles("A06,sysadmin")) {
SqlExpressionGroup group = new SqlExpressionGroup();
if (ShiroUtil.hasAnyRoles("H01")) {
@@ -155,7 +160,7 @@ public class ActivityApplyController {
}
if (!ShiroUtil.hasAnyRoles("H04,gh08,sysadmin")) {
if (!ActivityRoleUtil.isUnionManager() && !ShiroUtil.hasAnyRoles("gh08,sysadmin")) {
cnd.and(new Static("JSON_CONTAINS(school.applyWay,JSON_ARRAY( 1))>0"));
cnd.and(new Static("(SELECT COUNT(1) FROM activity_user_scope WHERE groupId=school.activityGroupId AND userId='%s') >0".formatted(ShiroUtil.getUserId())));
}
@@ -238,12 +243,13 @@ public class ActivityApplyController {
WHERE
asa.eventId = @eventId
AND asa.activityId=@activityId
AND asa.activityUnionId = @unionid
AND (asa.activityUnionId = @unionid OR asa.applyUser = @applyUser)
AND asa.`status` != 3
ORDER BY
asa.identity DESC,
asa.sex
""").setParam("eventId", eventId).setParam("unionid", union_id).setParam("activityId", activityId);
""").setParam("eventId", eventId).setParam("unionid", union_id)
.setParam("applyUser", ShiroUtil.getUserId()).setParam("activityId", activityId);
List<ActivitySchoolApply> list = activitySchoolApplyService.listEntity(applySql);
//团体人数
List<ActivitySchoolApply> awardsModeT = list.stream().filter(v -> v.getAwardsMode().equals("2")).collect(Collectors.toList());
@@ -348,6 +354,7 @@ public class ActivityApplyController {
*/
@At
@ViReturn
@Aop(TransAop.READ_COMMITTED)
public Object doApply(@Param("data") ActivitySchoolApply[] applies, @Param("appliedData3") ActivitySchoolApply[] applies2, String eventId, boolean pass) {
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
Sys_unit unit = user.getUnit();
@@ -375,16 +382,46 @@ public class ActivityApplyController {
apply2.setActivityUnionId(basicUnit.getUnionid());
apply2.setActivityUnionName(basicUnion.getUnionname());
}
if (applies.length > 0) {
activitySchoolApplyService.doApplies(activityId, basicUnit.getUnionid(), eventId, Arrays.asList(applies));
}
if (applies2.length > 0) {
activitySchoolApplyService.doApplies(activityId, basicUnit.getUnionid(), eventId, Arrays.asList(applies2));
if (applies.length > 0 || applies2.length > 0) {
List<ActivitySchoolApply> allApplies = new ArrayList<>(Arrays.asList(applies));
allApplies.addAll(Arrays.asList(applies2));
activitySchoolApplyService.doApplies(activityId, basicUnit.getUnionid(), eventId, allApplies);
}
return null;
}
/**
* 创建报名专用人员;工号留空时由服务端生成,手动填写时服务端负责查重。
* @param username 外援姓名
* @param sex 性别,男或女
* @param loginname 可为空;留空时自动生成,手动填写时不能与现有账号重复
* @param idcard 18位身份证号,出生日期由服务端从身份证号读取
* @param mobile 手机号码
* @param fitForCompetition 身体条件是否适合参赛,只有 true 可创建
* @return Sys_user,id 为报名人员 ID,loginname 为最终工号,其他字段用于页面回显
*/
@At
@ViReturn
@RequiresPermissions("activity.apply")
@Aop(TransAop.READ_COMMITTED)
public Object addExternalUser(String username, String loginname, String sex, String idcard, String mobile,
Boolean fitForCompetition) {
if (!Boolean.TRUE.equals(fitForCompetition)) {
throw new IllegalArgumentException("身体条件不适合参赛,不能添加外援");
}
Sys_user operator = (Sys_user) ShiroUtil.getPrincipal();
return activitySchoolApplyService.createExternalUser(username, loginname, sex, idcard, mobile, operator.getUnitid());
}
/** @return String,当前年份的下一个外援工号预览,实际编号以创建接口返回值为准。 */
@At
@ViReturn
@RequiresPermissions("activity.apply")
public Object previewExternalLoginname() {
return activitySchoolApplyService.previewExternalLoginname();
}
/**
* 个人报名
*
@@ -611,7 +648,7 @@ public class ActivityApplyController {
""").setParam("unionId", getUnionId()).setParam("userId", ShiroUtil.getUserId());
if (!ShiroUtil.hasAnyRoles("H04,gh08,sysadmin")) {
if (!ActivityRoleUtil.isUnionManager() && !ShiroUtil.hasAnyRoles("gh08,sysadmin")) {
cnd.where().andLike("school.applyWay ", "1");
cnd.and(new Static("(SELECT COUNT(1) FROM activity_user_scope WHERE groupId=school.activityGroupId AND userId='%s') >0".formatted(ShiroUtil.getUserId())));
@@ -645,116 +682,39 @@ public class ActivityApplyController {
/**
* 如果当前项目是综合类活动就添加工会总领队和教练
* 保存综合类活动的领队、教练及工作人员;取消选择的活动级身份会删除。
*
* @param LeaderData
* @param CoachData
* @return
* @param activityId 当前活动ID
* @param staffData 工作人员报名信息,userId 为所选用户ID,空值表示取消
* @param LeaderData 领队报名信息,userId 为所选用户ID,空值表示取消
* @param CoachData 教练报名信息,userId 为所选用户ID,空值表示取消
* @param headData 团长报名信息,userId 为所选用户ID,空值表示取消
* @return null,由 @ViReturn 包装为操作结果
*/
@At
@ViReturn
public Object doLeaderCoachHead(@Param("staffData") ActivitySchoolApply staffData, @Param("LeaderData") ActivitySchoolApply LeaderData, @Param("CoachData") ActivitySchoolApply CoachData, @Param("headData") ActivitySchoolApply headData) {
if (staffData.getUserId() != null) {
ActivityBasicUnit basicUnit = activitySchoolService.dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", staffData.getUnitId()));
ActivityBasicUnion basicUnion = activitySchoolService.dao().fetch(ActivityBasicUnion.class, Cnd.where("id", "=", basicUnit.getUnionid()));
activitySchoolApplyService.clear(Cnd.where("activityId", "=", staffData.getActivityId()).and("unionStaff", "=", true).and("unionId", "=", getUnionId()));
staffData.setApplyUser(ShiroUtil.getPrincipalProperty("id").toString());
staffData.setApplyDate(DateUtil.getDate());
staffData.setUnitname(basicUnit.getName());
staffData.setActivityUnionId(basicUnion.getId());
staffData.setActivityUnionName(basicUnion.getUnionname());
activitySchoolApplyService.insert(staffData);
}
if (LeaderData.getUserId() != null) {
ActivityBasicUnit basicUnit = activitySchoolService.dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", LeaderData.getUnitId()));
ActivityBasicUnion basicUnion = activitySchoolService.dao().fetch(ActivityBasicUnion.class, Cnd.where("id", "=", basicUnit.getUnionid()));
activitySchoolApplyService.clear(Cnd.where("activityId", "=", LeaderData.getActivityId()).and("unionLeader", "=", true).and("unionId", "=", getUnionId()));
LeaderData.setApplyUser(ShiroUtil.getPrincipalProperty("id").toString());
LeaderData.setApplyDate(DateUtil.getDate());
LeaderData.setUnitname(basicUnit.getName());
LeaderData.setActivityUnionId(basicUnion.getId());
LeaderData.setActivityUnionName(basicUnion.getUnionname());
activitySchoolApplyService.insert(LeaderData);
}
if (CoachData.getUserId() != null) {
ActivityBasicUnit basicUnit = activitySchoolService.dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", CoachData.getUnitId()));
ActivityBasicUnion basicUnion = activitySchoolService.dao().fetch(ActivityBasicUnion.class, Cnd.where("id", "=", basicUnit.getUnionid()));
activitySchoolApplyService.clear(Cnd.where("activityId", "=", CoachData.getActivityId()).and("unionCoach", "=", true).and("unionId", "=", getUnionId()));
CoachData.setApplyUser(ShiroUtil.getPrincipalProperty("id").toString());
CoachData.setApplyDate(DateUtil.getDate());
CoachData.setUnitname(basicUnit.getName());
CoachData.setActivityUnionId(basicUnion.getId());
CoachData.setActivityUnionName(basicUnion.getUnionname());
activitySchoolApplyService.insert(CoachData);
}
if (headData.getUserId() != null) {
ActivityBasicUnit basicUnit = activitySchoolService.dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", headData.getUnitId()));
ActivityBasicUnion basicUnion = activitySchoolService.dao().fetch(ActivityBasicUnion.class, Cnd.where("id", "=", basicUnit.getUnionid()));
activitySchoolApplyService.clear(Cnd.where("activityId", "=", headData.getActivityId()).and("unionHead", "=", true).and("unionId", "=", getUnionId()));
headData.setApplyUser(ShiroUtil.getPrincipalProperty("id").toString());
headData.setApplyDate(DateUtil.getDate());
headData.setUnitname(basicUnit.getName());
headData.setActivityUnionId(basicUnion.getId());
headData.setActivityUnionName(basicUnion.getUnionname());
activitySchoolApplyService.insert(headData);
}
@Aop(TransAop.READ_COMMITTED)
public Object doLeaderCoachHead(String activityId, @Param("staffData") ActivitySchoolApply staffData,
@Param("LeaderData") ActivitySchoolApply LeaderData,
@Param("CoachData") ActivitySchoolApply CoachData,
@Param("headData") ActivitySchoolApply headData) {
activitySchoolApplyService.saveUnionRoles(activityId, getUnionId(), ShiroUtil.getUserId(),
staffData, LeaderData, CoachData, headData);
return null;
}
/**
* 查询活动中当前工会的领队、教练和工作人员,并提供可回显的用户选项。
*
* @param activityId 当前活动ID
* @return Map,unionCoach、unionLeader、unionHead、unionStaff 为对应报名记录,
* userOptions 为有效用户列表;历史空 userId 仅在返回值中按唯一工号回显
*/
@At
@ViReturn
public Object getUnionCoachLeaderHead(String activityId) {
Map result = new HashMap();
Sql sql = Sqls.create("""
SELECT apply.*,us.unitname FROM activity_school_apply apply LEFT JOIN `user` us ON us.id=apply.userId WHERE apply.activityId=@activityId and apply.unionId=@unionId
""");
sql.setParam("unionId", getUnionId());
sql.setParam("activityId", activityId);
List<NutMap> list = activitySchoolApplyService.listMap(sql);
NutMap unionCoach = list.stream().filter(v -> {
return v.getBoolean("unionCoach");
}).findFirst().orElse(null);
NutMap unionLeader = list.stream().filter(v -> {
return v.getBoolean("unionLeader");
}).findFirst().orElse(null);
NutMap unionHead = list.stream().filter(v -> {
return v.getBoolean("unionHead");
}).findFirst().orElse(null);
NutMap unionStaff = list.stream().filter(v -> {
return v.getBoolean("unionStaff");
}).findFirst().orElse(null);
result.put("unionCoach", unionCoach);
result.put("unionLeader", unionLeader);
result.put("unionHead", unionHead);
result.put("unionStaff", unionStaff);
List userOptions = List.of();
ArrayList arrayList = new ArrayList<>(userOptions);
if (unionCoach != null) {
arrayList.add(sysUserService.fetch(unionCoach.getString("userId")));
}
if (unionLeader != null) {
arrayList.add(sysUserService.fetch(unionLeader.getString("userId")));
}
if (unionHead != null) {
arrayList.add(sysUserService.fetch(unionHead.getString("userId")));
}
if (unionStaff != null) {
arrayList.add(sysUserService.fetch(unionStaff.getString("userId")));
}
result.put("userOptions", arrayList);
return result;
return activitySchoolApplyService.getUnionRoleInfo(activityId, getUnionId());
}
@At
@@ -816,25 +776,18 @@ public class ActivityApplyController {
return insert.getId();
}
/**
* 查询添加外援时可复用的系统人员。
* @param queryString 姓名或工号片段;空值不查询
* @param userId 不参与返回的用户 ID 列表,可为空
* @return List<NutMap>,每项包含 id、username、loginname、sex、idcard、birthday、mobile、unitid、unitname
*/
@At
@ViReturn
@RequiresPermissions("activity.apply")
@Ok("json:{ignoreNull:true}")
public Object getSysUser(String queryString, String[] userId) {
Cnd cnd = Cnd.NEW();
cnd.andEX("id", "not in", userId);
if (StrUtil.isNotBlank(queryString)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.andLike("username", queryString).orLike("loginname", queryString);
cnd.and(group);
}
// cnd.and("unionid", "=", Vi.getUnionId());
Sql sql = Sqls.create("select id,loginname,username,sex,mobile,unitid,birthday from `user` $condition LIMIT 30");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.entities());
sql.setEntity(sysUserService.getEntity());
List<Sys_user> list = sysUserService.execute(sql).getList(Sys_user.class);
return list;
return activitySchoolApplyService.searchSystemUsers(queryString, userId);
}
@@ -2,6 +2,7 @@ package io.v.nutz.activity.controller.school;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.hutool.core.util.StrUtil;
import com.deepoove.poi.XWPFTemplate;
@@ -11,14 +12,19 @@ import io.v.nutz.activity.models.*;
import io.v.nutz.activity.services.ActivityPlanService;
import io.v.nutz.activity.services.ActivitySchoolApplyService;
import io.v.nutz.activity.services.ActivitySchoolService;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.dao.CndPlus;
import io.v.nutz.service.ViService;
import io.v.nutz.sys.models.Sys_union;
import io.v.nutz.sys.models.Sys_unit;
import io.v.nutz.sys.models.Sys_user;
import io.v.nutz.sys.services.SysUnionService;
import io.v.nutz.util.ShiroUtil;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.nutz.dao.Cnd;
@@ -164,18 +170,9 @@ public class ActivityCommonController {
return activityPlanService.listMap(Sqls.create("select * from activity_plan where type = @type").setParam("type", type));
}
private String getUnionId() {
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
Sys_unit unit = user.getUnit();
return unit == null ? "" : unit.getUnionid();
}
@At
@ViReturn
public Object findOne(String id) {
String unionid = getUnionId();
Sql sql = Sqls.create("""
SELECT
act.*,
@@ -210,21 +207,22 @@ public class ActivityCommonController {
SELECT
app.*,
team.`name` team,
unit.`name` unitname
COALESCE(NULLIF(app.unitname, ''), unit.`name`) unitname
FROM
activity_school_apply app
LEFT JOIN activity_school_team team ON app.teamId = team.id
LEFT JOIN sys_unit unit ON app.unitId = unit.id
WHERE
app.activityId = @id
app.activityId = @id
$union
ORDER BY
app.identity DESC,
team.location ASC,
app.sex
""").setParam("id", id);
""").setParam("id", id).setVar("union", "");
if (!ShiroUtil.hasAnyRoles("sysadmin,A06") && ShiroUtil.hasRole("H04")) {
sql1.setVar("union", " AND app.unionId = '" + unionid + "' ");
if (!ShiroUtil.hasAnyRoles("sysadmin,A06") && ActivityRoleUtil.isUnionManager()) {
sql1.setVar("union", " AND app.unionId IN " + ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId()));
}
List<NutMap> applies = activitySchoolApplyService.listMap(sql1);
@@ -236,8 +234,8 @@ public class ActivityCommonController {
Cnd unionCnd = Cnd.NEW();
if (!ShiroUtil.hasAnyRoles("sysadmin,A06") && ShiroUtil.hasRole("H04")) {
unionCnd.and("id", "=", unionid);
if (!ShiroUtil.hasAnyRoles("sysadmin,A06") && ActivityRoleUtil.isUnionManager()) {
unionCnd.and("id", "in", ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId()));
}
List<Sys_union> unions = sysUnionService.query(unionCnd);
@@ -265,33 +263,52 @@ public class ActivityCommonController {
@At
@Ok("void")
public void exportXlsx(String id, String unionId, HttpServletResponse response) throws IOException {
Sql sql = Sqls.create("""
SELECT
app.username,
app.loginname,
app.identity,
app.activityUnionName,
app.sex,
app.mobile,
u.idCard,
u.userState,
ev.allName,
sc.`name` activityName,
app.`team` teamName,
app.`unitname`
app.id,
app.activityId,
app.unionId,
app.userId,
app.eventId,
app.teamId,
app.username,
app.birthday,
COALESCE(NULLIF(app.loginname, ''), u.loginname) loginname,
app.identity,
app.activityUnionName,
app.sex,
COALESCE(NULLIF(app.mobile, ''), u.mobile) mobile,
COALESCE(NULLIF(app.idCard, ''), u.idCard) idcard,
COALESCE(unit.unitcode, u.unitcode) unitcode,
CASE WHEN u.member = 1 THEN '是' ELSE '否' END isMember,
CASE
WHEN u.userState IN ('在职', '在岗')
AND (u.preparedBy LIKE '%事业编%' OR u.preparedBy LIKE '%在编%')
AND u.preparedBy NOT LIKE '%非事业编%'
THEN '是' ELSE '否'
END isOnStaff,
apply_user.username applyUsername,
ev.allName,
sc.`name` activityName,
app.`team` teamName,
app.`unitname`
FROM
`activity_school_apply` app
LEFT JOIN `user` u ON u.id = app.userId
LEFT JOIN activity_event ev ON ev.id = app.eventId
LEFT JOIN activity_school sc ON sc.id=app.activityId
`activity_school_apply` app
LEFT JOIN `user` u ON u.id = app.userId
LEFT JOIN `user` apply_user ON apply_user.id = app.applyUser
LEFT JOIN sys_unit unit ON unit.id = app.unitId
LEFT JOIN activity_event ev ON ev.id = app.eventId
LEFT JOIN activity_school sc ON sc.id=app.activityId
WHERE
activityId = @id
$cnd
activityId = @id
$cnd
ORDER BY
ev.projectCode desc,
app.activityUnionName DESC,
app.`team` ASC,
COALESCE(unit.unitcode, u.unitcode) ASC,
ev.projectCode DESC,
app.activityUnionName DESC,
app.`team` ASC,
CASE
WHEN JSON_SEARCH( app.identity, 'one', '7' ) IS NOT NULL THEN
1
@@ -307,7 +324,7 @@ public class ActivityCommonController {
if (Strings.isNotBlank(unionId)) {
sql.setVar("cnd", "and u.unionid='" + unionId + "'");
}
List<NutMap> nutMaps = activitySchoolApplyService.listMap(sql);
List<NutMap> nutMaps = activitySchoolApplyService.mergeExportIdentities(activitySchoolApplyService.listMap(sql));
String activityName = nutMaps.get(0).getString("activityName");
List<ExcelExportEntity> entityList = new ArrayList<>();
@@ -320,8 +337,12 @@ public class ActivityCommonController {
entityList.add(new ExcelExportEntity("工号", "loginname", 20));
entityList.add(new ExcelExportEntity("姓名", "username", 20));
entityList.add(new ExcelExportEntity("性别", "sex", 10));
entityList.add(new ExcelExportEntity("出生年月", "birthday", 20));
entityList.add(new ExcelExportEntity("身份证号", "idcard", 20));
entityList.add(new ExcelExportEntity("电话", "mobile", 20));
entityList.add(new ExcelExportEntity("报名人电话号码", "mobile", 20));
entityList.add(new ExcelExportEntity("是否会员", "isMember", 15));
entityList.add(new ExcelExportEntity("是否在职在编", "isOnStaff", 20));
entityList.add(new ExcelExportEntity("填报人", "applyUsername", 20));
entityList.add(new ExcelExportEntity("所在单位", "unitname", 50));
entityList.add(new ExcelExportEntity("身份", "sf", 30));
@@ -331,11 +352,55 @@ public class ActivityCommonController {
z.put("sf", sf.replace("[", "").replace("]", ""));
});
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=" + new String((activityName + "报名表.xls").getBytes("utf-8"), "ISO8859-1"));
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entityList, nutMaps);
response.setHeader("Content-Disposition", "attachment;filename=" + new String((activityName + "报名表.xlsx").getBytes("utf-8"), "ISO8859-1"));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, nutMaps);
markMissingRequiredInfo(workbook);
workbook.write(response.getOutputStream());
}
/**
* 对导出名单中缺失的必要信息进行醒目标记。
*/
private void markMissingRequiredInfo(Workbook workbook) {
Sheet sheet = workbook.getSheetAt(0);
Row headerRow = sheet.getRow(0);
if (headerRow == null) {
return;
}
Set<String> requiredHeaders = new HashSet<>(Arrays.asList("工号", "身份证号", "报名人电话号码"));
List<Integer> requiredColumns = new ArrayList<>();
for (Cell cell : headerRow) {
if (requiredHeaders.contains(cell.getStringCellValue())) {
requiredColumns.add(cell.getColumnIndex());
}
}
Map<Short, CellStyle> warningStyles = new HashMap<>();
for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
Row row = sheet.getRow(rowIndex);
if (row == null) {
continue;
}
for (Integer columnIndex : requiredColumns) {
Cell cell = row.getCell(columnIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
if (Strings.isBlank(cell.toString())) {
cell.setCellValue("信息缺失");
CellStyle warningStyle = warningStyles.computeIfAbsent(cell.getCellStyle().getIndex(), key -> {
CellStyle style = workbook.createCellStyle();
style.cloneStyleFrom(cell.getCellStyle());
style.setFillForegroundColor(IndexedColors.LIGHT_ORANGE.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
return style;
});
cell.setCellStyle(warningStyle);
}
}
}
}
private static class personType {
@@ -384,10 +449,8 @@ public class ActivityCommonController {
""");
if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) {
if (ShiroUtil.hasRole("H04")) {
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
Sys_unit unit = user.getUnit();
unionCnd.and("un.id", "=", unit.getUnionid());
if (ActivityRoleUtil.isUnionManager()) {
unionCnd.and("un.id", "in", ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId()));
}
} else {
unionCnd.andExcludeEmpty("un.id", "=", unionId);
@@ -402,7 +465,7 @@ public class ActivityCommonController {
ae.allName,
app.*,
team.`name` teamName,
unit.`name` unitName,
COALESCE(NULLIF(app.unitname, ''), unit.`name`) unitName,
gh.unionname AS unionName
FROM
activity_school_apply app
@@ -588,7 +651,7 @@ public class ActivityCommonController {
SELECT
app.*,
team.`name` team,
unit.`name` unitname
COALESCE(NULLIF(app.unitname, ''), unit.`name`) unitname
FROM
activity_school_apply app
LEFT JOIN activity_school_team team ON app.teamId = team.id
@@ -179,8 +179,8 @@ public class ActivityGradesController {
Cnd cnd = Cnd.NEW();
Sql sql = Sqls.create("""
SELECT
us.unitname,
apply.*
apply.*,
COALESCE(NULLIF(apply.unitname, ''), us.unitname) unitname
FROM
activity_school_apply apply
LEFT JOIN `user` us ON apply.userId = us.id $condition
@@ -3,6 +3,7 @@ package io.v.nutz.activity.controller.school;
import cn.hutool.core.util.StrUtil;
import io.v.nutz.activity.models.*;
import io.v.nutz.activity.services.ActivitySchoolService;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.result.Result;
import io.v.nutz.base.utils.DateUtil;
@@ -15,6 +16,8 @@ import io.v.nutz.util.ShiroUtil;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Chain;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
@@ -56,6 +59,38 @@ public class ActivityInfoManageController {
public void index() {
}
/** 无参数;返回 data 数组,每项包含可沿用活动的 id 和 name。 */
@At
@ViReturn
@RequiresPermissions("activity.school.info")
public Object getReuseActivityOptions() {
return activitySchoolService.getReuseActivityOptions();
}
/** id 为来源活动ID;返回 data 对象,包含活动字段和 schoolEvents 项目配置数组。 */
@At
@ViReturn
@RequiresPermissions("activity.school.info")
public Object findReuseActivity(String id) {
return activitySchoolService.findReuseActivity(id);
}
/**
* sourceId 为来源活动ID,data 为新活动表单,events 为项目配置数组。
* 返回 data 字符串(新活动ID),供保存后继续编辑使用。
*/
@At
@ViReturn
@RequiresPermissions("activity.school.info")
@Aop(TransAop.READ_COMMITTED)
public Object doReuseAdd(String sourceId, @Param("data") ActivitySchool data,
@Param("events") ActivitySchoolEvent[] events) {
if (StrUtil.isBlank(sourceId) || data == null || data.getActivityLevel() == null) {
return Result.error("请选择沿用活动并填写活动信息");
}
return activitySchoolService.doReuseAdd(sourceId, data, events);
}
@At
@ViReturn
public Object pageData(PageForm page, Integer year, String name, String gameStyle, String query, String activityId, String activityLevel) {
@@ -103,9 +138,13 @@ public class ActivityInfoManageController {
} else if ("four".equals(query)) {
cnd.and(new Static("now() > school.endTime"));
}
// 时间状态快捷查询只展示已提交活动;历史记录的 isSave 可能为空。
if ("one".equals(query) || "two".equals(query) || "three".equals(query) || "four".equals(query)) {
cnd.and(new Static("(school.isSave IS NULL OR school.isSave <> 1)"));
}
if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) {
if (ShiroUtil.hasRole("H04")) {
cnd.and("school.belongUnionId", "=", Vi.getUnionId());
if (ActivityRoleUtil.isUnionManager()) {
cnd.and("school.belongUnionId", "in", ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId()));
}
if (ShiroUtil.hasRole("H02")) {
cnd.and("school.belongClubId", "in", vi.getMangeClubStr());
@@ -183,7 +222,7 @@ public class ActivityInfoManageController {
public Object doEdit(@Param("data") ActivitySchool activitySchool, @Param("events") ActivitySchoolEvent[] events) {
ActivitySchool school = activitySchoolService.fetch(activitySchool.getId());
if (school.getActivityLevel() == 40002) {
school.setBelongUnionId(Vi.getUnionId());
activitySchool.setBelongUnionId(ActivityRoleUtil.getCurrentUnionId(activitySchoolService.dao()));
}
activitySchoolService.doEdit(activitySchool, events, school.getApplyEndTime());
@@ -10,6 +10,7 @@ import io.v.nutz.activity.controller.school.constant.SchoolActivityApplyExportEn
import io.v.nutz.activity.models.*;
import io.v.nutz.activity.services.ActivitySchoolApplyService;
import io.v.nutz.activity.services.ActivitySchoolService;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.service.BaseService;
import io.v.nutz.base.utils.DateUtil;
@@ -89,7 +90,11 @@ public class ActivityReadingController {
cnd.and("activityLevel", "=", activityLevel);
if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) {
if (activityLevel.equals("40002")) {
cnd.and("belongUnionId", "=", getUnionId());
if (ActivityRoleUtil.isUnionManager()) {
cnd.and("belongUnionId", "in", ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId()));
} else {
cnd.and("belongUnionId", "=", getUnionId());
}
}
if (activityLevel.equals("40003")) {
cnd.and("belongClubId", "in", vi.getMangeClubStr());
@@ -107,55 +112,30 @@ public class ActivityReadingController {
ActivityBasicUnit basicUnit = activitySchoolService.dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", unitId));
ActivityBasicUnion basicUnion = activitySchoolService.dao().fetch(ActivityBasicUnion.class, Cnd.where("id", "=", basicUnit.getUnionid()));
ActivitySchool activitySchool = activitySchoolService.fetch(id);
Cnd cnd = Cnd.NEW();
Sql sql = Sqls.create("""
SELECT
app.*,
school.`name` activityName,
school.applyType applyType,
school.belongUnionId belongUnionId,
school.belongClubId belongClubId,
team.`name` team,
unit.`name` unitname,
ev.allName,
ev.id evid,
ev.projectType
FROM
activity_school_apply app
LEFT JOIN activity_school school ON school.id = app.activityId
LEFT JOIN activity_school_team team ON app.teamId = team.id
LEFT JOIN activity_event ev ON ev.id = app.eventId
LEFT JOIN sys_unit unit ON app.unitId = unit.id
$condition
""");
cnd.and("app.activityId", "=", id);
// cnd.and("YEAR(app.applyDate)", "=", year);
sql.setCondition(cnd);
List<NutMap> applies = activitySchoolApplyService.listRepresentedApplies(id);
if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) {
if (ShiroUtil.hasAnyRoles("H04,gh08") && activityLevel.equals("40002")) {
cnd.and("app.activityUnionId", "=", basicUnit.getUnionid());
cnd.and("school.belongUnionId", "=", basicUnit.getUnionid());
if ((ActivityRoleUtil.isUnionManager() || ShiroUtil.hasRole("gh08")) && activityLevel.equals("40002")) {
// 权限仍按活动所属工会限制;报名分组按外援选中的单位工会计算。
applies = basicUnit.getUnionid().equals(activitySchool.getBelongUnionId())
? applies.stream().filter(v -> basicUnit.getUnionid().equals(v.getString("representedActivityUnionId")))
.collect(Collectors.toList()) : List.of();
}
if (ShiroUtil.hasRole("H02") && activityLevel.equals("40003")) {
cnd.and("school.belongClubId", "in", vi.getMangeClubStr());
if (activitySchoolService.count(Cnd.where("id", "=", id)
.and("belongClubId", "in", vi.getMangeClubStr())) == 0) {
applies = List.of();
}
}
}
cnd.desc("ev.allName").asc("ev.projectType").asc("team.`name`");
List<NutMap> applies = activitySchoolApplyService.listMap(sql);
List<NutMap> apply = new ArrayList();
Cnd unionCnd = Cnd.NEW();
if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) {
if (ShiroUtil.hasAnyRoles("H04,gh08")) {
if (ActivityRoleUtil.isUnionManager() || ShiroUtil.hasRole("gh08")) {
unionCnd.and("id", "=", basicUnit.getUnionid());
}
}
if (activityLevel.equals("40002")) {
unionCnd.and("id", "=", activitySchool.getBelongUnionId());
}
unionCnd.and("isEnable", "=", true);
unionCnd.asc("unioncode");
if (!activityLevel.equals("40003")) {
@@ -163,10 +143,20 @@ public class ActivityReadingController {
for (ActivityBasicUnion union : unions) {
String unionId = union.getId();
boolean hasApplies = applies.stream()
.anyMatch(v -> unionId.equals(v.getString("representedActivityUnionId")));
if (!union.isEnable() && !hasApplies) {
continue;
}
// 院级活动保留主办工会,同时展示由所选单位代表其他工会出战的报名。
if (activityLevel.equals("40002") && !unionId.equals(activitySchool.getBelongUnionId())
&& !hasApplies) {
continue;
}
NutMap map = new NutMap();
List<NutMap> applyList = applies.stream().filter(v -> {
return v.getString("activityUnionId").equals(unionId);
return unionId.equals(v.getString("representedActivityUnionId"));
}).collect(Collectors.toList());
map.setv("id", unionId);
map.setv("unionname", union.getUnionname());
@@ -178,7 +168,7 @@ public class ActivityReadingController {
}
} else {
NutMap map = new NutMap();
Sys_club sysClub = sysClubService.fetch(cnd.where("id", "=", activitySchool.getBelongClubId()));
Sys_club sysClub = sysClubService.fetch(Cnd.where("id", "=", activitySchool.getBelongClubId()));
map.setv("id", sysClub.getId());
map.setv("unionname", sysClub.getName());
map.setv("unioncode", sysClub.getCode());
@@ -225,6 +215,9 @@ public class ActivityReadingController {
Sql sql = Sqls.create("""
SELECT
apply.loginname,
apply.birthday,
apply.mobile,
apply.unitname,
apply.username,
apply.sex,
IF(apply.sex='男',1,2) AS sexOrder,
@@ -238,12 +231,16 @@ public class ActivityReadingController {
FROM
`activity_school_apply` apply
LEFT JOIN activity_event `event` ON `event`.id = apply.eventId
LEFT JOIN sys_unit selectedUnit ON selectedUnit.id = apply.unitId
LEFT JOIN activity_basic_unit activityUnit ON activityUnit.id = apply.unitId
LEFT JOIN activity_basic_settings groupSetting ON groupSetting.id = `event`.competitionCategory
LEFT JOIN activity_basic_settings projectNameSetting ON projectNameSetting.id = `event`.sports
LEFT JOIN activity_basic_settings distanceSetting ON distanceSetting.id = `event`.distance
WHERE
apply.activityId = @id
AND apply.unionId = @unionId
AND (CASE WHEN apply.unitname = selectedUnit.name AND selectedUnit.unionid IS NOT NULL
AND activityUnit.unionid IS NOT NULL THEN selectedUnit.unionid
ELSE apply.unionId END) = @unionId
AND JSON_CONTAINS(apply.identity,JSON_Array( '1' ))
ORDER BY
FIELD( apply.sex, '男', '女 ' ),
@@ -264,6 +261,10 @@ public class ActivityReadingController {
for (int i = 0; i < v.size(); i++) {
switch (i) {
case 0 -> {
entity.setLoginname(v.get(i).getString("loginname"));
entity.setBirthday(v.get(i).getString("birthday"));
entity.setMobile(v.get(i).getString("mobile"));
entity.setUnitname(v.get(i).getString("unitname"));
if (v.get(i).getInt("awardsMode") == 1) {
entity.setUserName(v.get(i).getString("username"));
entity.setSex(v.get(i).getString("sex"));
@@ -3,6 +3,7 @@ package io.v.nutz.activity.controller.school;
import io.v.nutz.activity.models.ActivitySchoolEvent;
import io.v.nutz.activity.services.ActivitySchoolApplyService;
import io.v.nutz.activity.services.ActivitySchoolService;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.utils.RCSCloudAPI;
import io.v.nutz.base.utils.Roles;
@@ -22,10 +23,12 @@ import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -118,38 +121,41 @@ public class ActivityRemindController {
un.unionname,
(
SELECT
GROUP_CONCAT(su.mobile)
GROUP_CONCAT(DISTINCT su.mobile)
FROM
sys_user_role sur
LEFT JOIN sys_user su ON su.id = sur.userId
LEFT JOIN sys_role sr ON sr.id = sur.roleId
WHERE
sur.unionid = un.id
AND sr.id = @roleId
AND sr.id IN (@h04RoleId, @activityRoleId)
) telephone,
(
SELECT
GROUP_CONCAT(su.username)
GROUP_CONCAT(DISTINCT su.username)
FROM
sys_user_role sur
LEFT JOIN sys_user su ON su.id = sur.userId
LEFT JOIN sys_role sr ON sr.id = sur.roleId
WHERE
sur.unionid = un.id
AND sr.id = @roleId
) unionleader,
( SELECT COUNT( 1 ) FROM activity_school_apply asp WHERE asp.unionId = un.id AND asp.activityId = @activityId ) num
AND sr.id IN (@h04RoleId, @activityRoleId)
) unionleader
FROM
`sys_union` un
ORDER BY
un.unioncode
""").setParam("roleId", Roles.UNION_MANGER).setParam("activityId", activityId);
""").setParam("h04RoleId", Roles.UNION_MANGER)
.setParam("activityRoleId", Roles.SPORTS_ACTIVITY_UNION_ADMIN);
List<Record> unions = sysUnionService.list(sql);
Map<String, List<NutMap>> appliesByUnion = activitySchoolApplyService.groupRepresentedAppliesByUnion(activityId);
unions.forEach(v -> {
v.set("users", activitySchoolApplyService.query(Cnd.where("unionId", "=", v.getString("id")).and("activityId", "=", activityId).desc("identity")));
List<NutMap> users = appliesByUnion.getOrDefault(v.getString("id"), List.of());
v.set("users", users);
v.set("num", users.size());
});
return unions;
@@ -174,30 +180,34 @@ public class ActivityRemindController {
un.unionname,
(
SELECT
su.mobile
GROUP_CONCAT(DISTINCT su.mobile)
FROM
sys_user_role sur
LEFT JOIN sys_user su ON su.id = sur.userId
LEFT JOIN sys_role sr ON sr.id = sur.roleId
WHERE
sur.unionid = un.id
AND sr.id = @roleId
) telephone,
( SELECT COUNT( 1 ) FROM activity_school_apply asp WHERE asp.unionId = un.id AND asp.activityId = @activityId ) num
AND sr.id IN (@h04RoleId, @activityRoleId)
) telephone
FROM
`sys_union` un
ORDER BY
un.unioncode
""").setParam("roleId", Roles.UNION_MANGER).setParam("activityId", activityId);
""").setParam("h04RoleId", Roles.UNION_MANGER)
.setParam("activityRoleId", Roles.SPORTS_ACTIVITY_UNION_ADMIN);
List<Record> unions = sysUnionService.list(sql);
Map<String, List<NutMap>> appliesByUnion = activitySchoolApplyService.groupRepresentedAppliesByUnion(activityId);
List<Record> collect = unions.stream().filter(v -> {
return v.getInt("num") <= 0 && v.getString("telephone") != null;
return appliesByUnion.getOrDefault(v.getString("id"), List.of()).isEmpty()
&& v.getString("telephone") != null;
}).collect(Collectors.toList());
collect.forEach(v -> {
RCSCloudAPI.sendTplSms("399ac7a7901c49df9f5939d0f10d566b", v.getString("telephone"), "@1@=" + v.getString("unionname") + "", "");
for (String phone : v.getString("telephone").split(",")) {
RCSCloudAPI.sendTplSms("399ac7a7901c49df9f5939d0f10d566b", phone, "@1@=" + v.getString("unionname") + "", "");
}
});
return null;
@@ -208,8 +218,8 @@ public class ActivityRemindController {
public Object getActivitys() {
Cnd cnd = Cnd.NEW();
if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) {
if (ShiroUtil.hasRole("H04")) {
cnd.and("school.belongUnionId", "=", Vi.getUnionId());
if (ActivityRoleUtil.isUnionManager()) {
cnd.and("school.belongUnionId", "in", ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId()));
}
if (ShiroUtil.hasRole("H02")) {
cnd.and("school.belongClubId", "in", vi.getMangeClubStr());
@@ -5,6 +5,7 @@ import io.v.nutz.activity.models.ActivityBasicUnion;
import io.v.nutz.activity.models.ActivityBasicUnit;
import io.v.nutz.activity.models.ActivityResults;
import io.v.nutz.activity.models.ActivitySchoolApply;
import io.v.nutz.activity.services.ActivitySchoolApplyService;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.base.utils.Vi;
@@ -47,6 +48,9 @@ public class ActivityResultsController {
@Inject("ActivitySchoolApply")
private ViService<ActivitySchoolApply> activitySchoolApplyViService;
@Inject
private ActivitySchoolApplyService activitySchoolApplyService;
@At("")
@Ok("beetl:platform/activity/school/ActivityResults.html")
@@ -195,32 +199,14 @@ public class ActivityResultsController {
/**
* 查询团体参赛部门工会
*
* @param activityId
* @param eventId
* @return
* activityId、eventId 为页面当前活动和项目ID,仅保留接口参数兼容原调用。
* 返回全部系统工会列表,每项包含 id 和 unionname,供团体成绩录入选择。
*/
@At
@ViReturn
public Object getUnionList(String activityId, String eventId) {
Sql sql = Sqls.create("""
SELECT
un.id,
un.unionname
FROM
activity_school_apply asa
LEFT JOIN sys_union un ON un.id = asa.unionId
WHERE
asa.activityId = @activityId
AND asa.eventId = @eventId
GROUP BY
un.unionname
""").setParam("activityId", activityId).setParam("eventId", eventId);
return activitySchoolApplyViService.list(sql);
return activitySchoolApplyService.getResultUnionOptions();
}
@Inject("Sys_user")
@@ -52,6 +52,9 @@ public class ActivityReviewController {
apply.loginname,
apply.username,
apply.mobile,
apply.sex,
apply.birthday,
apply.unitname,
apply.identity,
school.id schoolId,
school.eventsId,
@@ -18,6 +18,14 @@ public class SchoolActivityApplyExportEntity {
private String number;
@Excel(name = "姓名", width = 20d)
private String userName;
@Excel(name = "工号", width = 20d)
private String loginname;
@Excel(name = "出生年月", width = 20d)
private String birthday;
@Excel(name = "电话", width = 20d)
private String mobile;
@Excel(name = "单位", width = 20d)
private String unitname;
@Excel(name = "性别", width = 20d)
private String sex;
@@ -10,6 +10,7 @@ import io.v.nutz.activity.models.ActivityTissue;
import io.v.nutz.activity.models.ActivityTissuePerson;
import io.v.nutz.activity.services.ActivitySchoolService;
import io.v.nutz.activity.services.ActivityTissueService;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.base.utils.Vi;
@@ -101,20 +102,27 @@ public class ActivityUnionManageController {
CndPlus cnd = CndPlus.create();
Sql sql = Sqls.create("""
SELECT id userId,username userName,loginname loginName,sex,mobile,unitname unitName,unionname unionName FROM `user` $condition
SELECT u.id userId,u.username userName,u.loginname loginName,u.sex,u.mobile,u.unitname unitName,u.unionname unionName
FROM `user` u
LEFT JOIN activity_basic_unit un ON un.id = u.unitid
$condition
""");
if (!ShiroUtil.hasRole("sysadmin")) {
if (activity_type == 40002) {
cnd.and("unionId", "=", vi.getUnionId());
if (ActivityRoleUtil.isUnionManager()) {
cnd.and("un.unionId", "in", ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId()));
} else {
cnd.and("u.unionId", "=", vi.getUnionId());
}
} else if (activity_type == 40003) {
cnd.and("id", "in", " SELECT userid FROM sys_club_user WHERE clubid = '%s' ".formatted(vi.getClubId()));
cnd.and("u.id", "in", " SELECT userid FROM sys_club_user WHERE clubid = '%s' ".formatted(vi.getClubId()));
}
}
if (Strings.isNotBlank(serachWord)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.andLike("username", serachWord);
group.orLike("loginname", serachWord);
group.andLike("u.username", serachWord);
group.orLike("u.loginname", serachWord);
cnd.and(group);
}
@@ -136,7 +144,7 @@ public class ActivityUnionManageController {
tissue.setApplyTime(DateUtil.getDate());
if (tissue.getType() == 40002) {
tissue.setState(1);
tissue.setUnionId(Vi.getUnionId());
tissue.setUnionId(ActivityRoleUtil.getCurrentUnionId(activityTissueService.dao()));
} else if (tissue.getType() == 40003) {
tissue.setState(1);
tissue.setClubId(vi.getClubId());
@@ -2,6 +2,7 @@ package io.v.nutz.activity.controller.union;
import io.v.nutz.activity.models.ActivityPlan;
import io.v.nutz.activity.services.ActivityPlanService;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.dao.CndPlus;
@@ -87,7 +88,7 @@ public class ActivityUnionPlanController {
activityPlan.setActivityType(activityType);
if (type.equals("40002")) {
activityPlan.setType("40002");
activityPlan.setTissueId(Vi.getUnionId());
activityPlan.setTissueId(ActivityRoleUtil.getCurrentUnionId(activityPlanService.dao()));
} else if (type.equals("40003")) {
activityPlan.setType("40003");
activityPlan.setTissueId(vi.getClubId());
@@ -5,6 +5,7 @@ import cn.wizzer.framework.base.Result;
import cn.wizzer.framework.base.service.BaseService;
import io.v.nutz.activity.models.ActivityYearSummary;
import io.v.nutz.activity.services.ActivityYearSummaryService;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.utils.Roles;
import io.v.nutz.base.utils.Vi;
@@ -105,7 +106,11 @@ public class ActivityUnionYearSummaryController {
if ("2".equals(summaryFrom)) {
str += " left join sys_union un on su.tissueId = un.id";
if (!ShiroUtil.hasAnyRoles(new String[]{"sysadmin", "A06"})) {
cnd.and("su.tissueId", "=", Vi.getUnionId());
if (ActivityRoleUtil.isUnionManager()) {
cnd.and("su.tissueId", "in", ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId()));
} else {
cnd.and("su.tissueId", "=", Vi.getUnionId());
}
}
}
if ("3".equals(summaryFrom)) {
@@ -168,6 +168,9 @@ public class ActivitySchoolApply implements Serializable {
private String mobile;
@Column
@Comment("报名时使用的身份证号,系统资料为空时保存本次手动补充值")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Excel(name = "身份证号", width = 20)
private String idCard;
@@ -1,18 +1,96 @@
package io.v.nutz.activity.services;
import io.v.nutz.activity.models.ActivitySchoolApply;
import io.v.nutz.sys.models.Sys_user;
import io.v.nutz.service.ViService;
import org.nutz.lang.util.NutMap;
import java.util.List;
import java.util.Map;
/**
* @author Aaron
*/
public interface ActivitySchoolApplyService extends ViService<ActivitySchoolApply> {
/**
* 获取团体成绩录入可选的全部系统工会,不受活动项目报名情况或启用状态限制。
* @return 工会列表,每项包含 id(成绩记录使用的工会ID)和 unionname(下拉显示名称)
*/
List<NutMap> getResultUnionOptions();
/** @return 当前年份预计分配的外援工号;实际工号以创建接口返回值为准。 */
String previewExternalLoginname();
/**
* 按姓名或工号查找可复用的系统人员。
* @param queryString 搜索内容,空值返回空列表
* @param excludedUserIds 当前页面需要排除的用户 ID
* @return 人员列表,每项包含 id、姓名、工号、性别、身份证号、出生日期、电话和系统单位
*/
List<NutMap> searchSystemUsers(String queryString, String[] excludedUserIds);
/**
* 查询活动报名及其代表出战的工会;历史报名仅在所存单位 ID 与单位名称一致时按该单位归属。
* @param activityId 活动 ID
* @return 报名列表,representedUnionId 是系统工会 ID,representedActivityUnionId 是活动工会 ID,
* 其余字段为报名、活动、项目和队伍的展示字段
*/
List<NutMap> listRepresentedApplies(String activityId);
/**
* 按代表出战的系统工会归集活动报名,供工会提醒名单和报名人数共用。
* @param activityId 活动 ID
* @return 以系统工会 ID 为键、该工会报名记录列表为值的 Map
*/
Map<String, List<NutMap>> groupRepresentedAppliesByUnion(String activityId);
/**
* 创建仅用于报名的用户;工号可采用 wy 年份流水号,也可手动填写。
* @param username 外援姓名
* @param sex 性别,男或女
* @param loginname 留空时自动生成的工号;手动填写时不能与系统现有工号重复
* @param idcard 身份证号,出生日期从中读取
* @param mobile 手机号码
* @param unitId 报名人所在单位 ID,用于归属当前报名工会
* @return 已创建的系统用户,包含用户 ID、最终工号和系统资料
*/
Sys_user createExternalUser(String username, String loginname, String sex, String idcard, String mobile, String unitId);
void doApplies(List<ActivitySchoolApply> applies);
void doApplies(String activity, String union_id, String eventId,List<ActivitySchoolApply> applies);
void doApply(ActivitySchoolApply apply);
/**
* 保存活动级工会人员;空 userId 表示取消该身份,项目内的报名记录不受影响。
*
* @param activityId 活动ID
* @param unionId 当前工会ID
* @param operatorId 当前操作人的用户ID
* @param staffData 工作人员报名信息,空 userId 表示取消
* @param leaderData 领队报名信息,空 userId 表示取消
* @param coachData 教练报名信息,空 userId 表示取消
* @param headData 团长报名信息,空 userId 表示取消
*/
void saveUnionRoles(String activityId, String unionId, String operatorId, ActivitySchoolApply staffData,
ActivitySchoolApply leaderData, ActivitySchoolApply coachData, ActivitySchoolApply headData);
/**
* 查询活动级工会人员及姓名下拉选项,历史空用户ID仅在返回数据中按唯一工号补齐。
*
* @param activityId 活动ID
* @param unionId 当前工会ID
* @return 包含 unionCoach、unionLeader、unionHead、unionStaff 报名记录及 userOptions 用户列表的 Map
*/
Map<String, Object> getUnionRoleInfo(String activityId, String unionId);
/**
* 合并同一人员在活动级和同一项目队伍中的导出身份,保留不同项目和队伍。
*
* @param rows 导出查询的报名记录,需包含活动、工会、人员、项目、队伍ID及 identity
* @return 每个项目队伍一行的导出记录;没有项目报名的活动级人员仍保留一行
*/
List<NutMap> mergeExportIdentities(List<NutMap> rows);
}
@@ -4,12 +4,23 @@ package io.v.nutz.activity.services;
import io.v.nutz.activity.models.ActivitySchool;
import io.v.nutz.activity.models.ActivitySchoolEvent;
import io.v.nutz.service.ViService;
import org.nutz.lang.util.NutMap;
import java.util.List;
/**
* @author Aaron
*/
public interface ActivitySchoolService extends ViService<ActivitySchool> {
/** 无参数;返回当前管理范围内可沿用活动的 id、name 列表,按创建时间倒序。 */
List<NutMap> getReuseActivityOptions();
/** id 为历史活动ID;返回活动字段及 schoolEvents 项目配置数组,不可访问时抛出异常。 */
NutMap findReuseActivity(String id);
/** sourceId 为来源活动ID,activitySchool 为新活动信息,events 为项目配置;返回新活动ID。 */
String doReuseAdd(String sourceId, ActivitySchool activitySchool, ActivitySchoolEvent[] events);
String doAdd(ActivitySchool activitySchool, ActivitySchoolEvent[] events);
void doEdit(ActivitySchool activitySchool, ActivitySchoolEvent[] events, String applyEndTime);
@@ -2,6 +2,7 @@ package io.v.nutz.activity.services.impl;
import io.v.nutz.activity.models.ActivityPlan;
import io.v.nutz.activity.services.ActivityPlanService;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.dao.CndPlus;
import io.v.nutz.modules.models.PageForm;
@@ -54,8 +55,8 @@ public class ActivityPlanServiceImpl extends ViServiceImpl<ActivityPlan> impleme
}
if (!ShiroUtil.hasAnyRoles("sysadmin,H10,A06")) {
if (ShiroUtil.hasRole("H04")) {
cnd.and("tissueId", "in", vi.getMangeUnionStr());
if (ActivityRoleUtil.isUnionManager()) {
cnd.and("tissueId", "in", ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId()));
} else if (ShiroUtil.hasRole("H02")) {
cnd.and("tissueId", "in", vi.getMangeClubStr());
}
@@ -1,25 +1,175 @@
package io.v.nutz.activity.services.impl;
import cn.hutool.core.util.IdcardUtil;
import io.v.nutz.activity.models.ActivityBasicUnion;
import io.v.nutz.activity.models.ActivityBasicUnit;
import io.v.nutz.activity.models.ActivitySchoolApply;
import io.v.nutz.activity.services.ActivitySchoolApplyService;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.service.impl.ViServiceImpl;
import io.v.nutz.sys.models.Sys_user;
import io.v.nutz.sys.models.Sys_unit;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import java.time.LocalDate;
import java.time.Year;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class ActivitySchoolApplyServiceImpl extends ViServiceImpl<ActivitySchoolApply> implements ActivitySchoolApplyService {
private static final Object EXTERNAL_NUMBER_LOCK = new Object();
public ActivitySchoolApplyServiceImpl(Dao dao) {
super(dao);
}
@Override
public List<NutMap> getResultUnionOptions() {
// 团体成绩可以选择未报名该项目的工会,因此从系统工会表完整读取。
return listMap(Sqls.create("SELECT id, unionname FROM sys_union ORDER BY unioncode"));
}
@Override
public String previewExternalLoginname() {
String prefix = "wy" + Year.now().getValue();
Sql sql = Sqls.create("SELECT loginname FROM sys_user WHERE loginname LIKE @prefix");
sql.setParam("prefix", prefix + "%");
long number = 0;
for (NutMap row : listMap(sql)) {
String loginname = row.getString("loginname");
if (Strings.isBlank(loginname) || !loginname.matches("^" + prefix + "[0-9]+$")) continue;
try {
number = Math.max(number, Long.parseLong(loginname.substring(prefix.length())));
} catch (NumberFormatException ignored) {
// 非法超长工号不参与流水号计算,保留其他有效工号的最大值。
}
}
return prefix + String.format("%02d", number + 1);
}
@Override
public List<NutMap> searchSystemUsers(String queryString, String[] excludedUserIds) {
String query = Strings.sNull(queryString).trim();
if (query.isEmpty()) return List.of();
Sql sql = Sqls.create("SELECT u.id,u.loginname,u.username,u.sex,u.idcard,u.birthday,u.mobile,"
+ "u.unitid,un.name unitname FROM sys_user u "
+ "LEFT JOIN sys_unit un ON un.id=u.unitid $condition LIMIT 30");
Cnd cnd = Cnd.NEW();
cnd.andEX("u.id", "not in", excludedUserIds);
SqlExpressionGroup names = new SqlExpressionGroup();
names.orLike("u.username", query).orLike("u.loginname", query);
cnd.and(names);
sql.setCondition(cnd);
return listMap(sql);
}
@Override
public List<NutMap> listRepresentedApplies(String activityId) {
// 兼容提交时曾写入操作人工会的外援记录;只有保存的单位 ID 和名称一致才采用所选单位的工会。
Sql sql = Sqls.create("""
SELECT app.*, school.name activityName, school.applyType, school.belongUnionId,
school.belongClubId, team.name team,
COALESCE(NULLIF(app.unitname, ''), unit.name) unitname,
ev.allName, ev.id evid, ev.projectType,
CASE WHEN app.unitname = unit.name AND unit.unionid IS NOT NULL
AND basicUnit.unionid IS NOT NULL THEN unit.unionid
ELSE app.unionId END representedUnionId,
CASE WHEN app.unitname = unit.name AND unit.unionid IS NOT NULL
AND basicUnit.unionid IS NOT NULL THEN basicUnit.unionid
ELSE app.activityUnionId END representedActivityUnionId
FROM activity_school_apply app
LEFT JOIN activity_school school ON school.id = app.activityId
LEFT JOIN activity_school_team team ON team.id = app.teamId
LEFT JOIN activity_event ev ON ev.id = app.eventId
LEFT JOIN sys_unit unit ON unit.id = app.unitId
LEFT JOIN activity_basic_unit basicUnit ON basicUnit.id = app.unitId
WHERE app.activityId = @activityId
ORDER BY ev.allName DESC, ev.projectType ASC, team.name ASC
""").setParam("activityId", activityId);
return listMap(sql);
}
@Override
public Map<String, List<NutMap>> groupRepresentedAppliesByUnion(String activityId) {
return listRepresentedApplies(activityId).stream()
.filter(row -> Strings.isNotBlank(row.getString("representedUnionId")))
.collect(Collectors.groupingBy(row -> row.getString("representedUnionId")));
}
@Override
@Aop(TransAop.READ_COMMITTED)
public Sys_user createExternalUser(String username, String loginname, String sex, String idcard, String mobile, String unitId) {
String name = Strings.sNull(username).trim();
String account = Strings.sNull(loginname).trim();
String card = Strings.sNull(idcard).trim().toUpperCase();
String phone = Strings.sNull(mobile).trim();
if (name.isEmpty() || name.length() > 50 || !("男".equals(sex) || "女".equals(sex))
|| !phone.matches("^1[3456789]\\d{9}$")) {
throw new IllegalArgumentException("请填写外援姓名、性别及正确的手机号码");
}
if (account.length() > 50) {
throw new IllegalArgumentException("工号不能超过50位");
}
if (!card.matches("^[0-9]{17}[0-9X]$") || !IdcardUtil.isValidCard(card)) {
throw new IllegalArgumentException("请填写正确的18位身份证号");
}
String birthday = card.substring(6, 10) + "-" + card.substring(10, 12) + "-" + card.substring(12, 14);
try {
LocalDate.parse(birthday);
} catch (DateTimeParseException | NullPointerException e) {
throw new IllegalArgumentException("身份证号中的出生日期不正确");
}
ActivityBasicUnit unit = dao().fetch(ActivityBasicUnit.class, unitId);
if (unit == null || Strings.isBlank(unit.getUnionid())) {
throw new IllegalArgumentException("当前报名单位未配置活动工会");
}
// 自动工号与手填工号都在同一锁内查重,避免并发创建出相同账号。
synchronized (EXTERNAL_NUMBER_LOCK) {
if (account.isEmpty()) account = previewExternalLoginname();
if (dao().count(Sys_user.class, Cnd.where("loginname", "=", account)) > 0) {
throw new IllegalArgumentException("工号已存在,请更换工号");
}
if (dao().count(Sys_user.class, Cnd.where("idcard", "=", card)) > 0) {
throw new IllegalArgumentException("身份证号已存在,请搜索并选择系统人员");
}
Sys_user user = new Sys_user();
user.setId(R.UU32());
user.setLoginname(account);
user.setUsername(name);
user.setSex(sex);
user.setBirthday(birthday);
user.setIdcard(card);
user.setMobile(phone);
user.setUnitid(unitId);
user.setUnionid(unit.getUnionid());
user.setDisabled(true);
dao().insert(user);
return user;
}
}
@Aop(TransAop.READ_COMMITTED)
public void doApplies(List<ActivitySchoolApply> applies) {
@@ -32,6 +182,55 @@ public class ActivitySchoolApplyServiceImpl extends ViServiceImpl<ActivitySchool
@Override
@Aop(TransAop.READ_COMMITTED)
public void doApplies(String activity, String union_id, String eventId,List<ActivitySchoolApply> applies) {
for (ActivitySchoolApply apply : applies) {
Sys_user user = Strings.isBlank(apply.getUserId()) ? null : dao().fetch(Sys_user.class, apply.getUserId());
if (Strings.isNotBlank(apply.getIdCard())) {
String card = apply.getIdCard().trim().toUpperCase();
if (!IdcardUtil.isValidCard(card)) {
throw new IllegalArgumentException("报名人员身份证号不正确");
}
apply.setIdCard(card);
}
if (user != null && user.isDisabled() && Strings.isNotBlank(user.getLoginname())
&& user.getLoginname().matches("wy[0-9]{6,}")) {
// 工号和姓名保持系统身份;身份证、出生日期和电话保留本次报名的纠错值。
apply.setLoginname(user.getLoginname());
apply.setUsername(user.getUsername());
if (Strings.isNotBlank(user.getSex())) apply.setSex(user.getSex());
if (Strings.isBlank(apply.getIdCard())) apply.setIdCard(user.getIdcard());
if (Strings.isBlank(apply.getBirthday())) apply.setBirthday(user.getBirthday());
if (Strings.isBlank(apply.getMobile())) apply.setMobile(user.getMobile());
if (Strings.isBlank(apply.getUnitId())) {
apply.setUnitId(user.getUnitid());
}
if (Strings.isBlank(apply.getUnitname())) {
throw new IllegalArgumentException("请填写外援报名单位");
}
}
if (Strings.isNotBlank(apply.getUnitId()) && Strings.isNotBlank(apply.getUnitname())) {
Sys_unit selectedUnit = dao().fetch(Sys_unit.class, apply.getUnitId());
// 单位来自系统下拉时,所属工会就是本次代表出战的工会;手填单位保留报名人工会。
if (selectedUnit != null && apply.getUnitname().trim().equals(selectedUnit.getName())) {
ActivityBasicUnit activityUnit = dao().fetch(ActivityBasicUnit.class, apply.getUnitId());
if (Strings.isBlank(selectedUnit.getUnionid()) || activityUnit == null
|| Strings.isBlank(activityUnit.getUnionid())) {
throw new IllegalArgumentException("所选单位未配置活动工会,不能确定代表出战的工会");
}
ActivityBasicUnion activityUnion = dao().fetch(ActivityBasicUnion.class, activityUnit.getUnionid());
if (activityUnion == null) {
throw new IllegalArgumentException("所选单位对应的活动工会不存在");
}
apply.setUnionId(selectedUnit.getUnionid());
apply.setActivityUnionId(activityUnion.getId());
apply.setActivityUnionName(activityUnion.getUnionname());
}
}
}
// 重存时清理本操作人以前代表其他工会报名的记录,避免换单位后留下重复报名。
if (!applies.isEmpty()) {
clear(Cnd.where("activityId", "=", activity).and("eventId", "=", eventId)
.and("applyUser", "=", applies.get(0).getApplyUser()).and("activityUnionId", "!=", union_id));
}
clear(Cnd.where("activityId", "=", activity).and("activityUnionId", "=", union_id).and("eventId","=",eventId));
doApplies(applies);
}
@@ -41,4 +240,159 @@ public class ActivitySchoolApplyServiceImpl extends ViServiceImpl<ActivitySchool
public void doApply(ActivitySchoolApply apply) {
insert(apply);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void saveUnionRoles(String activityId, String unionId, String operatorId, ActivitySchoolApply staffData,
ActivitySchoolApply leaderData, ActivitySchoolApply coachData, ActivitySchoolApply headData) {
saveUnionRole(activityId, unionId, operatorId, staffData, "unionStaff");
saveUnionRole(activityId, unionId, operatorId, leaderData, "unionLeader");
saveUnionRole(activityId, unionId, operatorId, coachData, "unionCoach");
saveUnionRole(activityId, unionId, operatorId, headData, "unionHead");
}
/**
* 仅替换活动级身份。先清除旧记录,使空 userId 能表示取消,同时不碰项目队伍身份。
*/
@Aop(TransAop.READ_COMMITTED)
private void saveUnionRole(String activityId, String unionId, String operatorId, ActivitySchoolApply role, String roleColumn) {
clear(Cnd.where("activityId", "=", activityId).and("unionId", "=", unionId)
.and("eventId", "IS", null).and(roleColumn, "=", true));
if (role == null || Strings.isBlank(role.getUserId())) {
return;
}
Sys_user user = dao().fetch(Sys_user.class, role.getUserId());
ActivityBasicUnit unit = dao().fetch(ActivityBasicUnit.class, role.getUnitId());
if (user == null || unit == null || Strings.isBlank(unit.getUnionid())) {
throw new IllegalArgumentException("所选人员或单位不存在,请重新选择");
}
ActivityBasicUnion union = dao().fetch(ActivityBasicUnion.class, unit.getUnionid());
if (union == null) {
throw new IllegalArgumentException("所选人员的活动工会不存在");
}
role.setActivityId(activityId);
role.setEventId(null);
role.setTeamId(null);
role.setUnionId(unionId);
role.setApplyUser(operatorId);
role.setApplyDate(DateUtil.getDate());
role.setUnitname(unit.getName());
role.setActivityUnionId(union.getId());
role.setActivityUnionName(union.getUnionname());
role.setUsername(user.getUsername());
role.setLoginname(user.getLoginname());
if ("unionStaff".equals(roleColumn)) role.setUnionStaff(true);
if ("unionLeader".equals(roleColumn)) role.setUnionLeader(true);
if ("unionCoach".equals(roleColumn)) role.setUnionCoach(true);
if ("unionHead".equals(roleColumn)) role.setUnionHead(true);
insert(role);
}
@Override
public Map<String, Object> getUnionRoleInfo(String activityId, String unionId) {
Sql sql = Sqls.create("SELECT apply.*,us.unitname FROM activity_school_apply apply "
+ "LEFT JOIN `user` us ON us.id=apply.userId "
+ "WHERE apply.activityId=@activityId AND apply.unionId=@unionId AND apply.eventId IS NULL");
sql.setParam("activityId", activityId);
sql.setParam("unionId", unionId);
List<NutMap> rows = listMap(sql);
NutMap coach = rows.stream().filter(row -> Boolean.TRUE.equals(row.getBoolean("unionCoach"))).findFirst().orElse(null);
NutMap leader = rows.stream().filter(row -> Boolean.TRUE.equals(row.getBoolean("unionLeader"))).findFirst().orElse(null);
NutMap head = rows.stream().filter(row -> Boolean.TRUE.equals(row.getBoolean("unionHead"))).findFirst().orElse(null);
NutMap staff = rows.stream().filter(row -> Boolean.TRUE.equals(row.getBoolean("unionStaff"))).findFirst().orElse(null);
Map<String, Object> result = new LinkedHashMap<>();
result.put("unionCoach", coach);
result.put("unionLeader", leader);
result.put("unionHead", head);
result.put("unionStaff", staff);
Map<String, Sys_user> options = new LinkedHashMap<>();
// 历史记录的 userId 可能为空;工号只在唯一匹配时用于回显,不在查询时改写数据库。
for (NutMap role : Arrays.asList(coach, leader, head, staff)) {
if (role == null) continue;
Sys_user user = Strings.isBlank(role.getString("userId")) ? null : dao().fetch(Sys_user.class, role.getString("userId"));
if (user == null && Strings.isNotBlank(role.getString("loginname"))) {
List<Sys_user> matches = dao().query(Sys_user.class, Cnd.where("loginname", "=", role.getString("loginname")));
if (matches.size() == 1) user = matches.get(0);
}
if (user != null) {
role.put("userId", user.getId());
options.put(user.getId(), user);
}
}
result.put("userOptions", new ArrayList<>(options.values()));
return result;
}
@Override
public List<NutMap> mergeExportIdentities(List<NutMap> rows) {
Map<String, Set<String>> accountUsers = new LinkedHashMap<>();
for (NutMap row : rows) {
if (Strings.isNotBlank(row.getString("loginname")) && Strings.isNotBlank(row.getString("userId"))) {
accountUsers.computeIfAbsent(exportAccountKey(row), key -> new LinkedHashSet<>()).add(row.getString("userId"));
}
}
Map<String, Set<String>> activityRoles = new LinkedHashMap<>();
Map<String, Set<String>> projectRoles = new LinkedHashMap<>();
Map<String, NutMap> projectRows = new LinkedHashMap<>();
Set<String> peopleWithProjects = new LinkedHashSet<>();
for (NutMap row : rows) {
String person = exportPersonKey(row, accountUsers);
List<String> identity = Lang.map2Object(row, ActivitySchoolApply.class).getIdentity();
String project = exportProjectKey(row, person);
if (project == null) {
activityRoles.computeIfAbsent(person, key -> new LinkedHashSet<>()).addAll(identity == null ? List.of() : identity);
} else {
peopleWithProjects.add(person);
projectRows.putIfAbsent(project, row);
projectRoles.computeIfAbsent(project, key -> new LinkedHashSet<>()).addAll(identity == null ? List.of() : identity);
}
}
List<NutMap> result = new ArrayList<>();
Set<String> emitted = new LinkedHashSet<>();
for (NutMap row : rows) {
String person = exportPersonKey(row, accountUsers);
String project = exportProjectKey(row, person);
if (project == null && peopleWithProjects.contains(person)) continue;
String outputKey = project == null ? "activity:" + person : "project:" + project;
if (!emitted.add(outputKey)) continue;
NutMap source = project == null ? row : projectRows.get(project);
NutMap merged = new NutMap();
merged.putAll(source);
Set<String> identities = new LinkedHashSet<>();
if (project != null) identities.addAll(projectRoles.get(project));
identities.addAll(activityRoles.getOrDefault(person, Set.of()));
merged.put("identity", new ArrayList<>(identities));
result.add(merged);
}
return result;
}
/** 工号和姓名用于关联历史空 userId 记录;发现多个账号共用该组合时,保留各自记录。 */
private String exportPersonKey(NutMap row, Map<String, Set<String>> accountUsers) {
String union = Strings.sNull(row.getString("unionId"));
String loginname = row.getString("loginname");
String userId = row.getString("userId");
if (Strings.isNotBlank(loginname)) {
String account = exportAccountKey(row);
if (accountUsers.getOrDefault(account, Set.of()).size() > 1) {
return Strings.isNotBlank(userId) ? union + ":user:" + userId : union + ":row:" + row.getString("id");
}
return account;
}
if (Strings.isNotBlank(userId)) return union + ":user:" + userId;
return union + ":row:" + row.getString("id");
}
private String exportAccountKey(NutMap row) {
return Strings.sNull(row.getString("unionId")) + ":login:" + row.getString("loginname")
+ ":name:" + Strings.sNull(row.getString("username"));
}
/** 同一项目中不同队伍仍是不同报名行;活动级身份没有项目和队伍。 */
private String exportProjectKey(NutMap row, String person) {
if (Strings.isBlank(row.getString("eventId"))) return null;
return person + ":event:" + row.getString("eventId") + ":team:" + Strings.sNull(row.getString("teamId"));
}
}
@@ -6,11 +6,18 @@ import io.v.nutz.activity.services.ActivitySchoolApplyService;
import io.v.nutz.activity.services.ActivitySchoolService;
import io.v.nutz.activity.services.ActivitySchoolTeamService;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.util.ShiroUtil;
import io.v.nutz.service.ViService;
import io.v.nutz.service.impl.ViServiceImpl;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Chain;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -46,12 +53,127 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl<ActivitySchool> imp
@Inject("ActivitySchoolEvent")
private ViService<ActivitySchoolEvent> activitySchoolEventViService;
@Inject
private Vi vi;
/** 沿用列表和详情共用现有活动管理的数据范围,防止通过历史活动ID跨范围读取。 */
private Cnd reuseActivityCnd() {
Cnd cnd = Cnd.NEW();
if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) {
if (ActivityRoleUtil.isUnionManager()) {
cnd.and("belongUnionId", "in", ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId()));
}
if (ShiroUtil.hasRole("H02")) {
cnd.and("belongClubId", "in", vi.getMangeClubStr());
}
}
return cnd;
}
/** 返回当前用户管理范围内的历史活动ID和名称,按创建时间倒序排列。 */
@Override
public List<NutMap> getReuseActivityOptions() {
return listMap(Sqls.create("SELECT id, name FROM activity_school $condition")
.setCondition(reuseActivityCnd().desc("foundDate")));
}
/** id 为历史活动ID;详情包含原活动字段和 schoolEvents 数组,供前端复制业务配置。 */
@Override
public NutMap findReuseActivity(String id) {
if (Strings.isBlank(id) || fetch(reuseActivityCnd().and("id", "=", id)) == null) {
throw new IllegalArgumentException("沿用活动不存在或无权访问");
}
NutMap result = fetch(Sqls.create("SELECT * FROM activity_school WHERE id = @id").setParam("id", id));
result.setv("schoolEvents", listMap(Sqls.create("SELECT ase.*, ae.projectType FROM activity_school_event ase "
+ "LEFT JOIN activity_event ae ON ae.id = ase.eventId WHERE ase.activityId = @id").setParam("id", id)));
return result;
}
/**
* sourceId 为来源活动ID;只复制表单及项目配置,重新生成活动、配置、小队和组织记录。
* 返回新活动ID,所有写入使用同一事务,任何失败均回滚,不修改来源活动及报名记录。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public String doReuseAdd(String sourceId, ActivitySchool school, ActivitySchoolEvent[] events) {
findReuseActivity(sourceId);
if (school.getEventsIds() == null) {
school.setEventsIds(new java.util.ArrayList<>());
}
boolean isDraft = Integer.valueOf(1).equals(school.getIsSave());
if (!isDraft && school.getEventsIds().isEmpty()) {
throw new IllegalArgumentException("请选择活动项目");
}
ActivitySchoolEvent[] cleanEvents = Arrays.stream(events == null ? new ActivitySchoolEvent[0] : events)
.filter(event -> event != null && school.getEventsIds().contains(event.getEventId())
&& !"allItems".equals(event.getEventId()))
.peek(event -> {
// 配置主键和所属活动必须重新生成,不能信任前端传入的旧记录标识。
event.setId(null);
event.setActivityId(null);
}).toArray(ActivitySchoolEvent[]::new);
// 草稿允许暂不选择或配置项目;正式提交仍需验证项目可用且配置齐全。
if (!isDraft) {
for (String eventId : school.getEventsIds()) {
ActivityEvent event = dao().fetch(ActivityEvent.class, Cnd.where("id", "=", eventId).and("whetherEnable", "=", 1));
if (event == null) {
throw new IllegalArgumentException("所选项目已停用或不存在,请重新选择");
}
if (Arrays.stream(cleanEvents).noneMatch(config -> eventId.equals(config.getEventId()))) {
throw new IllegalArgumentException("项目配置不完整,请进入下一步完善项目信息");
}
}
}
school.setId(null);
school.setClose(false);
school.setFoundDate(DateUtil.getDate());
if (Integer.valueOf(40002).equals(school.getActivityLevel())) {
school.setBelongUnionId(ActivityRoleUtil.getCurrentUnionId(dao()));
} else {
school.setBelongUnionId(null);
}
if (school.getUndertakeUnit() == null) {
school.setUndertakeUnit(new java.util.ArrayList<>());
}
insert(school);
addTeam(school.getId(), cleanEvents);
// 与普通创建保持一致,创建可报名工会和活动组织记录,保留后续报名入口所需关联。
for (ActivityBasicUnion unit : dao().query(ActivityBasicUnion.class, Cnd.where("isEnable", "=", true))) {
ActivityUnion union = new ActivityUnion();
union.setActivityId(school.getId());
union.setUnionname(unit.getUnionname());
dao().insert(union);
}
ActivityTissue tissue = new ActivityTissue();
tissue.setApplyTime(school.getFoundDate());
tissue.setType(school.getActivityLevel());
tissue.setProjectTypeCode("50004");
tissue.setActivityCode(school.getActivityCode());
tissue.setName(school.getName());
tissue.setUserId(ShiroUtil.getUserId());
tissue.setState(3);
tissue.setClubId(school.getBelongClubId());
tissue.setUnionId(school.getBelongUnionId());
tissue.setAddress(school.getAddress());
tissue.setApplyStartTime(school.getApplyStartTime());
tissue.setApplyEndTime(school.getApplyEndTime());
tissue.setStartTime(school.getStartTime());
tissue.setEndTime(school.getEndTime());
if (school.getImage() != null && !school.getImage().isEmpty()) {
tissue.setCover(school.getImage().get(0).getId());
}
dao().insert(tissue);
// 与原创建入口一致,在插入后统一组织ID,避免实体 @Prev 的主键生成策略影响关联。
dao().update(ActivityTissue.class, Chain.make("id", school.getId()), Cnd.where("id", "=", tissue.getId()));
return school.getId();
}
@Override
@Aop(TransAop.READ_COMMITTED)
public String doAdd(ActivitySchool activitySchool, ActivitySchoolEvent[] events) {
activitySchool.setClose(false);
ActivitySchool school = insert(activitySchool);
addTeam(school.getId(), events);
addTeam(school.getId(), events == null ? new ActivitySchoolEvent[0] : events);
return school.getId();
}
@@ -59,7 +181,12 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl<ActivitySchool> imp
@Override
@Aop(TransAop.READ_COMMITTED)
public void doEdit(ActivitySchool activitySchool, ActivitySchoolEvent[] events, String applyEndTime) {
long time = DateUtil.formatDate(applyEndTime);
// 未填写报名结束时间的草稿也可再次保存;没有项目时按空配置处理。
long time = Strings.isBlank(applyEndTime) ? 0 : DateUtil.formatDate(applyEndTime);
if (activitySchool.getEventsIds() == null) {
activitySchool.setEventsIds(new java.util.ArrayList<>());
}
ActivitySchoolEvent[] safeEvents = events == null ? new ActivitySchoolEvent[0] : events;
long l = System.currentTimeMillis();
if (l < time) {
@@ -76,7 +203,7 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl<ActivitySchool> imp
return !activitySchool.getEventsIds().contains(v.getEventId());
}).collect(Collectors.toList());
List<ActivitySchoolEvent> collect1 = Lang.array2list(events).stream().filter(v -> {
List<ActivitySchoolEvent> collect1 = Lang.array2list(safeEvents).stream().filter(v -> {
return !query.stream().map(x -> x.getEventId()).collect(Collectors.toList()).contains(v.getEventId()) && (v.getId() == null || !v.getId().equals("allItems"));
}).collect(Collectors.toList());
@@ -91,7 +218,7 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl<ActivitySchool> imp
if (collect1.size() > 0) {
addTeam(activitySchool.getId(), collect1.toArray(new ActivitySchoolEvent[collect1.size()]));
}
for (ActivitySchoolEvent event : events) {
for (ActivitySchoolEvent event : safeEvents) {
update(event);
}
@@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
import io.v.nutz.activity.models.ActivityTissue;
import io.v.nutz.activity.models.ActivityTissuePerson;
import io.v.nutz.activity.services.ActivityTissueService;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.common.models.Goods;
import io.v.nutz.dao.CndPlus;
@@ -75,8 +76,8 @@ public class ActivityTissueServiceImpl extends ViServiceImpl<ActivityTissue> imp
}
if (!ShiroUtil.hasAnyRoles("sysadmin,H10,A06")) {
if (ShiroUtil.hasRole("H04")) {
cnd.and("tissue.unionId", "in", vi.getMangeUnionStr());
if (ActivityRoleUtil.isUnionManager()) {
cnd.and("tissue.unionId", "in", ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId()));
} else if (ShiroUtil.hasRole("H02")) {
cnd.and("tissue.clubId", "in", vi.getMangeClubStr());
}
@@ -0,0 +1,71 @@
package io.v.nutz.activity.utils;
import io.v.nutz.activity.models.ActivityBasicUnit;
import io.v.nutz.base.utils.Roles;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.sys.models.Sys_user;
import io.v.nutz.sys.models.Sys_user_role;
import org.apache.shiro.SecurityUtils;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.lang.Strings;
import java.util.List;
/**
* 体育活动角色工具
*/
public final class ActivityRoleUtil {
private ActivityRoleUtil() {
}
/**
* 原分工会管理员和体育活动联合工会管理员在体育活动范围内权限一致
*/
public static boolean isUnionManager() {
return SecurityUtils.getSubject().hasRole("H04")
|| SecurityUtils.getSubject().hasRole(Roles.SPORTS_ACTIVITY_UNION_ADMIN_CODE);
}
/**
* 获取用户在体育活动范围内管理的联合工会查询语句
*/
public static String getManageUnionStr(String userId) {
String safeUserId = userId == null ? "" : userId.replace("'", "''");
return "(SELECT sur.unionid FROM sys_user_role sur WHERE sur.userId = '" + safeUserId
+ "' AND sur.roleId IN ('" + Roles.UNION_MANGER + "','" + Roles.SPORTS_ACTIVITY_UNION_ADMIN + "'))";
}
/**
* 获取当前管理员正在管理的活动联合工会
*/
public static String getCurrentUnionId(Dao dao) {
Object principal = SecurityUtils.getSubject().getPrincipal();
if (!(principal instanceof Sys_user)) {
return "";
}
Sys_user user = (Sys_user) principal;
String activityUnionId = "";
if (user.getUnit() != null) {
ActivityBasicUnit basicUnit = dao.fetch(ActivityBasicUnit.class, user.getUnit().getId());
activityUnionId = basicUnit == null ? "" : Strings.sNull(basicUnit.getUnionid());
}
if (Strings.isNotBlank(activityUnionId)) {
Cnd currentRoleCnd = Cnd.where("userId", "=", user.getId())
.and("roleId", "in", new String[]{Roles.UNION_MANGER, Roles.SPORTS_ACTIVITY_UNION_ADMIN})
.and("unionid", "=", activityUnionId);
Sys_user_role currentRole = dao.fetch(Sys_user_role.class, currentRoleCnd);
if (currentRole != null) {
return activityUnionId;
}
}
Cnd roleCnd = Cnd.where("userId", "=", user.getId())
.and("roleId", "in", new String[]{Roles.UNION_MANGER, Roles.SPORTS_ACTIVITY_UNION_ADMIN})
.and("unionid", "IS NOT", null);
List<Sys_user_role> roles = dao.query(Sys_user_role.class, roleCnd);
return roles.isEmpty() ? Vi.getUnionId() : Strings.sNull(roles.get(0).getUnionid());
}
}
@@ -16,6 +16,21 @@ public interface Roles {
*/
String UNION_MANGER = "5d342e614e9a48288d50154bcdcc75d3";
/**
* 三级单位管理员
*/
String THIRD_UNIT_MANGER = "e8d957d1ab9f4e56a60b5a53929d18bb";
/**
* 体育活动联合工会管理员
*/
String SPORTS_ACTIVITY_UNION_ADMIN = "b72001e1783448edbba585b441955676";
/**
* 体育活动联合工会管理员角色编码
*/
String SPORTS_ACTIVITY_UNION_ADMIN_CODE = "SportsActivityUnionAdmin";
/**
* 分工会主席
*/
@@ -21,6 +21,8 @@ import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.entity.Record;
import org.nutz.dao.sql.Sql;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
@@ -67,16 +69,17 @@ public class ClothingRecordController {
@At
@RequiresAuthentication
@Aop(TransAop.READ_COMMITTED)
public Object doDelete(String projectId, String year, String userId) {
if(StrUtil.isBlank(projectId)) {
Result.error("项目不能为空!");
return Result.error("项目不能为空!");
}
if(StrUtil.isBlank(year)) {
Result.error("年份不能为空!");
return Result.error("年份不能为空!");
}
if(StrUtil.isBlank(userId)) {
Result.error("登记人不能为空!");
return Result.error("登记人不能为空!");
}
boolean b = clothingRegisterService.doDelete(projectId, year, userId);
return b ? Result.success(): Result.error();
@@ -104,6 +107,10 @@ public class ClothingRecordController {
return b ? Result.success(): Result.error();
}
/**
* year 为活动报名年度,projectId 为活动ID,userId 为登记人ID;旧项目记录按登记年度筛选。
* request 和 response 为当前 HTTP 请求及下载响应;返回值为 void,内容写入响应流。
*/
@At
@Ok("void")
public void doExport(String year, String projectId, String userId, HttpServletRequest request, HttpServletResponse response) throws IOException {
@@ -135,11 +142,13 @@ public class ClothingRecordController {
FROM
clothing_register c
LEFT JOIN `user` u ON u.id = c.buyUserId
LEFT JOIN activity_school s ON s.id = c.projectId
$condition
""");
cnd.and("year(ctime)", "=", year);
cnd.and("projectId", "=", projectId);
cnd.and("userId", "=", userId);
// 导出与台账采用同一业务年度,旧项目记录回退到登记年度。
cnd.and("COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime))", "=", year);
cnd.and("c.projectId", "=", projectId);
cnd.and("c.userId", "=", userId);
sql.setCondition(cnd);
List<Record> buyUserList = clothingRegisterService.list(sql);
for (int i = 0; i < buyUserList.size(); i++) {
@@ -13,6 +13,8 @@ import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.entity.Record;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
@@ -41,30 +43,43 @@ public class ClothingRegisterController {
@RequiresPermissions("clothing.register")
public void index() {}
/** pageForm 为分页和姓名工号条件;projectId 为活动ID;isBuy 为购买状态;sex、unionId 为可选筛选,返回人员分页数据。 */
@At
@ViReturn
public Object pageData(PageForm pageForm, Integer year, String projectId, Boolean isBuy, String sex, String unionId) {
public Object pageData(PageForm pageForm, String projectId, Boolean isBuy, String sex, String unionId) {
return clothingRegisterService.getRegisterUser(pageForm, year, projectId, isBuy, sex, unionId);
return clothingRegisterService.getRegisterUser(pageForm, projectId, isBuy, sex, unionId);
}
/**
* ids 为所选人员ID;projectId 为活动ID,购买限制以该活动的报名年度和已选项目为准;
* isBuy 为购买状态,true 时仅移除当前活动记录,false 时登记;
* 成功返回 null 由 ViReturn 包装,业务校验失败返回含错误说明的 Result。
*/
@At
@ViReturn
public Object doHandler(String[] ids, String projectId, Integer year, Boolean isBuy) {
@Aop(TransAop.READ_COMMITTED)
public Object doHandler(String[] ids, String projectId, Boolean isBuy) {
if(!isBuy) {
clothingRegisterService.add(ids, projectId);
} else {
clothingRegisterService.removeBuyUser(ids, projectId, year);
try {
if(!Boolean.TRUE.equals(isBuy)) {
clothingRegisterService.add(ids, projectId);
} else {
clothingRegisterService.removeBuyUser(ids, projectId);
}
} catch (IllegalArgumentException exception) {
// 重复购衣等业务校验错误直接返回给页面,供提交按钮显示明确原因。
return Result.error(exception.getMessage());
}
return null;
}
/** year 为空时返回全部已提交活动,否则按报名开始时间所属年度筛选;data 数组的每项包含活动 id 和 name。 */
@At
@ViReturn
public Object getBuyClothesProject() {
public Object getBuyClothesProject(Integer year) {
return clothingRegisterService.getBuyClothesProject();
return clothingRegisterService.getBuyClothesProject(year);
}
@At
@@ -1,7 +1,6 @@
package io.v.nutz.clothing.services;
import cn.wizzer.framework.page.Pagination;
import io.v.nutz.activity.models.ActivityBasicSettings;
import io.v.nutz.clothing.models.ClothingRegister;
import io.v.nutz.modules.models.PageForm;
import io.v.nutz.service.ViService;
@@ -20,29 +19,31 @@ public interface ClothingRegisterService extends ViService<ClothingRegister> {
/**
* 列表查询
*
* @param pageForm: 表单
* @param projectId: 项目id
* @param year: 年份
* @return io.v.nutz.base.page.Pagination
* @param pageForm: 分页及查询表单
* @param projectId: 活动ID,可为空
* @param year: 活动报名年度;旧项目记录按登记年度筛选
* @param unionId: 工会ID,可为空
* @return Pagination,list 为按活动、登记人和年度分组的台账,totalCount 为分组总数
* @author JyuHsin
* @date 2022/12/5
*/
Pagination pageData(PageForm pageForm, String projectId, String year, String unionId);
/**
* 获取所有买服装的项目
* @author JyuHsin
* @date 2022/12/5
* @return java.util.List<io.v.nutz.activity.models.ActivityBasicSettings>
* 获取已提交的校级活动,供服装登记、记录和台账选择项目。
* year 为空时返回全部年度,否则按活动报名开始时间所属年度筛选。
* 返回的每项包含活动 id(作为 projectId 保存和筛选)及 name(下拉显示名称)。
*/
List<ActivityBasicSettings> getBuyClothesProject();
List<NutMap> getBuyClothesProject(Integer year);
/**
* 获取项目人员名单
* @author JyuHsin
* @date 2022/12/5
* @param projectId: 项目id
* @return java.util.List<org.nutz.lang.util.NutMap>
* @param projectId: 活动ID
* @param userId: 登记人ID
* @param year: 活动报名年度,可为空;旧项目记录按登记年度筛选
* @return 人员列表,每项包含购买人资料及登记记录
*/
List<NutMap> findBuyClothesUser(String projectId, String userId, String year);
@@ -50,28 +51,25 @@ public interface ClothingRegisterService extends ViService<ClothingRegister> {
* 删除
* @author JyuHsin
* @date 2022/12/5
* @param projectId: 项目id
* @param year: 年份
* @param userId: 登记人
* @return void
* @param projectId: 活动ID
* @param year: 活动报名年度;旧项目记录按登记年度删除
* @param userId: 登记人ID
* @return 是否完成指定活动、年度和登记人的删除
*/
boolean doDelete(String projectId, String year, String userId);
/**
* 服装登记菜单查询用户
* @author JyuHsin
* @date 2022/12/5
* @param pageForm:
* @param year:
* @param projectId:
* @param isBuy:
* @return cn.wizzer.framework.page.Pagination
* 查询服装登记人员。pageForm 包含页码、页大小和姓名或工号查询条件;projectId 为当前活动ID;
* isBuy 为购买状态,true 返回与活动年度相差不超过一年且同类项目已购买的人员,false 返回未购买人员;
* sex 为可选性别,unionId 为可选工会ID。返回 Pagination,list 为人员及可移除状态,totalCount 为总人数。
*/
Pagination getRegisterUser(PageForm pageForm, Integer year, String projectId, Boolean isBuy, String sex, String unionId);
Pagination getRegisterUser(PageForm pageForm, String projectId, Boolean isBuy, String sex, String unionId);
/** ids 为本次登记的人员ID,projectId 为活动ID;两年内同类项目已有购买记录时拒绝提交。 */
Object add(String[] ids, String projectId);
boolean removeBuyUser(String[] ids, String projectId, Integer year);
/** ids 为人员ID,projectId 为当前活动ID;仅移除当前活动的登记记录,不删除其他同类活动的历史记录。 */
boolean removeBuyUser(String[] ids, String projectId);
boolean deleteBuyUser(String id);
}
@@ -2,7 +2,7 @@ package io.v.nutz.clothing.services.impl;
import cn.hutool.core.util.StrUtil;
import cn.wizzer.framework.page.Pagination;
import io.v.nutz.activity.models.ActivityBasicSettings;
import io.v.nutz.activity.models.ActivitySchool;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.clothing.models.ClothingRegister;
@@ -20,13 +20,18 @@ import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.Static;
import org.nutz.aop.interceptor.ioc.TransAop;
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 org.nutz.trans.Trans;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @Author JyuHsin
@@ -48,14 +53,15 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl<ClothingRegister>
Sql sql = Sqls.create("""
SELECT
c.*,
YEAR ( c.ctime ) AS `year`,
COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime)) AS `year`,
u.username,
u.unionname,
a.name AS projectName,
COALESCE(s.name, a.name) AS projectName,
count(c.buyUserId) AS buyCount
FROM
clothing_register c
LEFT JOIN `user` u ON u.id = c.userId
LEFT JOIN activity_school s ON s.id = c.projectId
LEFT JOIN activity_basic_settings a ON a.CODE = c.projectId
$condition
""");
@@ -65,8 +71,9 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl<ClothingRegister>
}
cnd.andExcludeEmpty("u.unionId", "=", unionId);
cnd.andExcludeEmpty("c.projectId", "=", projectId);
cnd.andExcludeEmpty("year(c.ctime)", "=", year);
cnd.groupBy("c.userId");
// 活动记录按报名年度归档;无法关联活动的旧记录仍按实际登记年度展示。
cnd.andExcludeEmpty("COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime))", "=", year);
cnd.groupBy("c.projectId", "c.userId", "COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime))");
cnd.desc("year").asc("projectId");
sql.setCondition(cnd);
@@ -74,8 +81,17 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl<ClothingRegister>
}
@Override
public List<ActivityBasicSettings> getBuyClothesProject() {
return dao().query(ActivityBasicSettings.class, Cnd.where("clothing", "is not", null));
public List<NutMap> getBuyClothesProject(Integer year) {
// isSave=1 是草稿;历史已提交活动可能为 NULL,需与活动管理页的判定保持一致。
CndPlus cnd = CndPlus.create();
cnd.and(new Static("(isSave IS NULL OR isSave <> 1)"));
cnd.andExcludeEmpty("YEAR(applyStartTime)", "=", year);
cnd.desc("startTime").desc("foundDate");
return listMap(Sqls.create("""
SELECT id, name
FROM activity_school
$condition
""").setCondition(cnd));
}
@Override
@@ -94,26 +110,38 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl<ClothingRegister>
FROM
clothing_register c
LEFT JOIN `user` u ON u.id = c.buyUserId
LEFT JOIN activity_school s ON s.id = c.projectId
$condition
""");
cnd.andExcludeEmpty("c.projectId", "=", projectId);
cnd.andExcludeEmpty("c.userId", "=", userId);
cnd.andExcludeEmpty("year(c.ctime)", "=", year);
cnd.andExcludeEmpty("COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime))", "=", year);
sql.setCondition(cnd);
return listMap(sql);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public boolean doDelete(String projectId, String year, String userId) {
int clearCount = dao().clear(ClothingRegister.class, Cnd.where("projectId", "=", projectId)
.and("year(ctime)", "=", year).and("userId", "=", userId));
Cnd cnd = Cnd.where("projectId", "=", projectId).and("userId", "=", userId);
ActivitySchool school = dao().fetch(ActivitySchool.class, projectId);
String applyStartTime = school == null ? null : school.getApplyStartTime();
// 新活动以报名年度定位整组登记;旧项目没有活动年度时沿用登记时间筛选。
if (Strings.isNotBlank(applyStartTime) && applyStartTime.length() >= 4) {
if (!year.equals(applyStartTime.substring(0, 4))) {
return false;
}
} else {
cnd.and("year(ctime)", "=", year);
}
int clearCount = dao().clear(ClothingRegister.class, cnd);
return clearCount >= 0;
}
@Override
public Pagination getRegisterUser(PageForm pageForm, Integer year, String projectId, Boolean isBuy, String sex, String unionId) {
public Pagination getRegisterUser(PageForm pageForm, String projectId, Boolean isBuy, String sex, String unionId) {
CndPlus cnd = CndPlus.create();
Sql sql = Sqls.create("""
@@ -129,20 +157,105 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl<ClothingRegister>
if (Strings.isNotBlank(pageForm.getSearchName()) && Strings.isNotBlank(pageForm.getSearchKeyword())) {
cnd.where().andLike(pageForm.getSearchName(), pageForm.getSearchKeyword());
}
if(year != null) {
int lastYear = year - 1;
cnd.and(new Static("id %s (select buyUserId from clothing_register where projectId = '%s' and (year(ctime) = '%s' or year(ctime) = '%s'))"
.formatted(isBuy ? "in" : "not in", projectId, year, lastYear)));
if (Strings.isBlank(projectId)) {
// 没有活动时不能把全部会员误显示为该活动的“未购买”人员。
cnd.and(new Static("1 = 0"));
} else {
// 活动名称和ID每年可能变化,购买状态按活动选中项目的运动项目及活动年度判断。
Set<String> purchasedIds = findPurchasedUserIds(projectId);
if (purchasedIds.isEmpty()) {
if (Boolean.TRUE.equals(isBuy)) cnd.and(new Static("1 = 0"));
} else {
cnd.and("u.id", Boolean.TRUE.equals(isBuy) ? "in" : "not in", purchasedIds.toArray(new String[0]));
}
}
sql.setCondition(cnd);
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
Pagination result = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
if (Boolean.TRUE.equals(isBuy) && Strings.isNotBlank(projectId)) {
// 历史活动中已购买的人员可查看,但只能移除当前活动自身的登记记录。
Set<String> currentIds = listMap(Sqls.create("SELECT DISTINCT buyUserId AS buy_user_id FROM clothing_register WHERE projectId = @projectId")
.setParam("projectId", projectId)).stream()
.map(row -> row.getString("buy_user_id")).collect(Collectors.toSet());
List<NutMap> users = result.getList();
users.forEach(user -> user.setv("canRemove", currentIds.contains(user.getString("id"))));
}
return result;
}
/**
* projectId 为当前活动ID;返回该活动选择的运动项目标识。项目未配置 sports 时以项目ID兜底,
* 避免把不同项目误判为同类;已选项目为空时拒绝登记,防止绕过重复购买限制。
*/
private List<String> getSelectedSportKeys(String projectId) {
List<NutMap> events = listMap(Sqls.create("""
SELECT DISTINCT COALESCE(NULLIF(ae.sports, ''), ae.id) AS sport_key
FROM activity_school_event ase
JOIN activity_event ae ON ae.id = ase.eventId
WHERE ase.activityId = @projectId
""").setParam("projectId", projectId));
List<String> keys = events.stream().map(event -> event.getString("sport_key"))
.filter(Strings::isNotBlank).distinct().collect(Collectors.toList());
if (keys.isEmpty()) throw new IllegalArgumentException("当前活动未配置可识别的活动项目,无法登记服装");
return keys;
}
/**
* projectId 为当前活动ID;返回与活动年度相差不超过一年、购买过任一相同运动项目服装的人员ID。
* 年度取活动报名开始时间,避免跨年补登记时因 clothing_register.ctime 不同而漏判。
*/
private Set<String> findPurchasedUserIds(String projectId) {
ActivitySchool school = dao().fetch(ActivitySchool.class, projectId);
if (school == null || Integer.valueOf(1).equals(school.getIsSave())) {
throw new IllegalArgumentException("活动不存在或尚未提交");
}
String applyStartTime = school.getApplyStartTime();
if (Strings.isBlank(applyStartTime) || applyStartTime.length() < 4) {
throw new IllegalArgumentException("当前活动未设置报名开始时间,无法判断两年购买范围");
}
int activityYear;
try {
activityYear = Integer.parseInt(applyStartTime.substring(0, 4));
} catch (NumberFormatException exception) {
throw new IllegalArgumentException("当前活动报名开始时间格式不正确", exception);
}
List<String> sportKeys = getSelectedSportKeys(projectId);
CndPlus cnd = CndPlus.create();
// 补录历史活动时也要检查后一年的购买记录,避免登记顺序改变查重结果。
cnd.and("YEAR(s.applyStartTime)", "in", new Integer[]{activityYear - 1, activityYear, activityYear + 1});
cnd.and("COALESCE(NULLIF(ae.sports, ''), ae.id)", "in", sportKeys.toArray(new String[0]));
Sql sql = Sqls.create("""
SELECT DISTINCT c.buyUserId AS buy_user_id
FROM clothing_register c
JOIN activity_school s ON s.id = c.projectId
JOIN activity_school_event ase ON ase.activityId = s.id
JOIN activity_event ae ON ae.id = ase.eventId
$condition
""").setCondition(cnd);
return listMap(sql).stream().map(row -> row.getString("buy_user_id"))
.filter(Strings::isNotBlank).collect(Collectors.toSet());
}
@Override
@Aop(TransAop.READ_COMMITTED)
public Object add(String[] ids, String projectId) {
if (ids == null || ids.length == 0 || Strings.isBlank(projectId)) {
throw new IllegalArgumentException("请选择活动和登记人员");
}
List<String> userIds = Arrays.stream(ids).filter(Strings::isNotBlank).distinct().sorted().collect(Collectors.toList());
if (userIds.isEmpty()) throw new IllegalArgumentException("请选择登记人员");
// 锁定人员行后再查重,防止并发提交同时通过两年内同项目的校验。
for (String id : userIds) {
if (listMap(Sqls.create("SELECT id FROM `user` WHERE id = @id FOR UPDATE").setParam("id", id)).isEmpty()) {
throw new IllegalArgumentException("所选人员不存在,请刷新名单");
}
}
Set<String> purchasedIds = findPurchasedUserIds(projectId);
if (userIds.stream().anyMatch(purchasedIds::contains)) {
throw new IllegalArgumentException("所选人员在近两年内已购买同类项目服装,不能重复提交,请刷新名单");
}
List<ClothingRegister> clothingRegisterList = new ArrayList<>();
for (String id : ids) {
for (String id : userIds) {
ClothingRegister clothingRegister = new ClothingRegister();
clothingRegister.setProjectId(projectId);
clothingRegister.setUserId(ShiroUtil.getPrincipalProperty("id").toString());
@@ -154,12 +267,17 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl<ClothingRegister>
}
@Override
public boolean removeBuyUser(String[] ids, String projectId, Integer year) {
@Aop(TransAop.READ_COMMITTED)
public boolean removeBuyUser(String[] ids, String projectId) {
if (ids == null || ids.length == 0 || Strings.isBlank(projectId)) {
throw new IllegalArgumentException("请选择当前活动中需要移除的人员");
}
Trans.exec(() -> {
for (String id : ids) {
dao().clear(ClothingRegister.class, Cnd.where("projectId", "=", projectId)
.and("year(ctime)", "=", year).and("buyUserId", "=", id));
Cnd cnd = Cnd.where("projectId", "=", projectId).and("buyUserId", "=", id);
// 同类历史活动的购买记录仍保留,仅删除当前活动中选中的登记。
dao().clear(ClothingRegister.class, cnd);
}
});
@@ -1,21 +1,31 @@
package io.v.nutz.diseaseaid.controller;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.diseaseaid.mode.AidFundMemberMode;
import io.v.nutz.diseaseaid.service.AidFundMemberAuditRecordService;
import io.v.nutz.diseaseaid.service.MedicalAidService;
import io.v.nutz.member.service.MemberService;
import io.v.nutz.modules.models.PageForm;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.entity.Record;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
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 javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -58,6 +68,7 @@ public class DiseaseAidDashBoard {
SELECT
un.unionname,
un.unioncode,
un.id unionid,
( SELECT count( 1 ) FROM `user` WHERE unionid = un.id and aidFundMember = @code ) `value`
FROM
sys_union un
@@ -188,5 +199,200 @@ public class DiseaseAidDashBoard {
return medicalAidService.count(sql);
}
/**
* 看板指标下钻明细
*/
@At
@ViReturn
@RequiresPermissions("aid.dashboard")
public Object detailPage(PageForm pageForm, String detailType, Integer year, Integer medicalType,
Integer stateId, String unionId, String unitId, String personType,
String userState, String sex, String searchKeyword,
String startTime, String endTime) {
Sql sql = buildDetailSql(detailType, year, medicalType, stateId, unionId, unitId,
personType, userState, sex, searchKeyword, startTime, endTime);
return medicalAidService.listPage(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
/**
* 导出当前筛选条件下的全部下钻明细
*/
@At
@Ok("void")
@RequiresPermissions("aid.dashboard")
public void exportDetail(HttpServletResponse response, String detailType, Integer year, Integer medicalType,
Integer stateId, String unionId, String unitId, String personType,
String userState, String sex, String searchKeyword,
String startTime, String endTime) throws IOException {
Sql sql = buildDetailSql(detailType, year, medicalType, stateId, unionId, unitId,
personType, userState, sex, searchKeyword, startTime, endTime);
List<Record> list = medicalAidService.list(sql);
List<ExcelExportEntity> columns = new ArrayList<>();
columns.add(new ExcelExportEntity("工号", "loginname", 18));
columns.add(new ExcelExportEntity("姓名", "username", 16));
columns.add(new ExcelExportEntity("性别", "sex", 10));
columns.add(new ExcelExportEntity("人员类型", "personType", 18));
columns.add(new ExcelExportEntity("在职状态", "userState", 16));
columns.add(new ExcelExportEntity("所属工会", "unionname", 25));
columns.add(new ExcelExportEntity("所属单位", "unitname", 25));
if ("medical".equals(detailType)) {
columns.add(new ExcelExportEntity("补助类型", "typeName", 18));
columns.add(new ExcelExportEntity("申请时间", "detailTime", 20));
columns.add(new ExcelExportEntity("申请状态", "state_name", 18));
columns.add(new ExcelExportEntity("补助金额", "total_grant", 18));
} else {
columns.add(new ExcelExportEntity("加入时间", "detailTime", 20));
}
String fileName = "medical".equals(detailType) ? "医疗互助申请明细.xls" : "基金会员明细.xls";
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename="
+ new String(fileName.getBytes("UTF-8"), "ISO8859-1"));
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), columns, list);
workbook.write(response.getOutputStream());
workbook.close();
}
/**
* 明细弹窗筛选选项
*/
@At
@ViReturn
@RequiresPermissions("aid.dashboard")
public Object detailOptions() {
Sql sql = Sqls.create("""
SELECT DISTINCT
s.state_id value,
s.state_name label
FROM state s
INNER JOIN medicalaid med ON med.state_id = s.state_id
ORDER BY s.state_id
""");
return Map.of("medicalStates", medicalAidService.list(sql));
}
private Sql buildDetailSql(String detailType, Integer year, Integer medicalType, Integer stateId,
String unionId, String unitId, String personType, String userState,
String sex, String searchKeyword, String startTime, String endTime) {
if ("medical".equals(detailType)) {
return buildMedicalDetailSql(year, medicalType, stateId, unionId, unitId, personType,
userState, sex, searchKeyword, startTime, endTime);
}
return buildMemberDetailSql(year, unionId, unitId, personType, userState, sex,
searchKeyword, startTime, endTime);
}
private Sql buildMemberDetailSql(Integer year, String unionId, String unitId, String personType,
String userState, String sex, String searchKeyword,
String startTime, String endTime) {
Sql sql = Sqls.create("""
SELECT
u.id,
u.loginname,
u.username,
u.sex,
u.personType,
u.userState,
u.unionid,
u.unionname,
u.unitid,
u.unitname,
u.aidFundMemberJoinTime detailTime
FROM user u
$condition
""");
Cnd cnd = Cnd.where("u.aidFundMember", "=", AidFundMemberMode.NORMAL.getCode());
if (year != null) {
cnd.and("YEAR(u.aidFundMemberJoinTime)", "=", year);
}
addUserCondition(cnd, "u", unionId, unitId, personType, userState, sex,
searchKeyword, startTime, endTime, "aidFundMemberJoinTime");
sql.setCondition(cnd.asc("u.unioncode").asc("u.loginname"));
return sql;
}
private Sql buildMedicalDetailSql(Integer year, Integer medicalType, Integer stateId,
String unionId, String unitId, String personType,
String userState, String sex, String searchKeyword,
String startTime, String endTime) {
Sql sql = Sqls.create("""
SELECT
med.id,
u.loginname,
med.user_name username,
u.sex,
u.personType,
u.userState,
u.unionid,
un.unionname,
u.unitid,
unit.name unitname,
med.type,
CASE med.type
WHEN 1 THEN '医疗补助'
WHEN 2 THEN '重疾补助'
WHEN 3 THEN '特殊门诊补助'
ELSE '其他'
END typeName,
med.apply_time detailTime,
med.state_id,
s.state_name,
med.total_grant
FROM medicalaid med
LEFT JOIN user u ON med.user_id = u.id
LEFT JOIN sys_union un ON u.unionid = un.id
LEFT JOIN sys_unit unit ON u.unitid = unit.id
LEFT JOIN state s ON med.state_id = s.state_id
$condition
""");
Cnd cnd = Cnd.NEW();
if (year != null) {
cnd.and("med.year", "=", year);
}
if (medicalType != null) {
cnd.and("med.type", "=", medicalType);
}
if (stateId != null) {
cnd.and("med.state_id", "=", stateId);
}
addUserCondition(cnd, "u", unionId, unitId, personType, userState, sex,
searchKeyword, startTime, endTime, "med.apply_time");
sql.setCondition(cnd.desc("med.apply_time"));
return sql;
}
private void addUserCondition(Cnd cnd, String alias, String unionId, String unitId,
String personType, String userState, String sex,
String searchKeyword, String startTime, String endTime,
String timeField) {
if (Strings.isNotBlank(unionId)) {
cnd.and(alias + ".unionid", "=", unionId);
}
if (Strings.isNotBlank(unitId)) {
cnd.and(alias + ".unitid", "=", unitId);
}
if (Strings.isNotBlank(personType)) {
cnd.and(alias + ".personType", "=", personType);
}
if (Strings.isNotBlank(userState)) {
cnd.and(alias + ".userState", "=", userState);
}
if (Strings.isNotBlank(sex)) {
cnd.and(alias + ".sex", "=", sex);
}
if (Strings.isNotBlank(startTime)) {
cnd.and(timeField, ">=", startTime);
}
if (Strings.isNotBlank(endTime)) {
cnd.and(timeField, "<=", endTime + " 23:59:59");
}
if (Strings.isNotBlank(searchKeyword)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike(alias + ".username", searchKeyword);
group.orLike(alias + ".loginname", searchKeyword);
cnd.and(group);
}
}
}
@@ -0,0 +1,12 @@
package io.v.nutz.levelcheck.constant;
/**
* 分级核对操作类型
*/
public interface LevelCheckAction {
String DISTRIBUTE = "DISTRIBUTE";
String CONFIRM = "CONFIRM";
String SUBMIT_REVISION = "SUBMIT_REVISION";
String APPROVE = "APPROVE";
String REJECT = "REJECT";
}
@@ -0,0 +1,12 @@
package io.v.nutz.levelcheck.constant;
/**
* 分级核对状态
*/
public interface LevelCheckState {
int PENDING_CHECK = 0;
int CONFIRMED = 1;
int PENDING_AUDIT = 2;
int REJECTED = 3;
int APPROVED = 4;
}
@@ -0,0 +1,184 @@
package io.v.nutz.levelcheck.controller;
import cn.wizzer.framework.base.Result;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.levelcheck.service.LevelCheckService;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.mvc.annotation.*;
import org.nutz.mvc.upload.TempFile;
import org.nutz.mvc.upload.UploadAdaptor;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* 校工会分级核对管理
*/
@IocBean
@At("/platform/levelCheck/school")
@Ok("json:full")
@RequiresAuthentication
public class LevelCheckSchoolController {
@Inject
private LevelCheckService levelCheckService;
@At("")
@Ok("beetl:/platform/levelCheck/school.html")
public void index() {
levelCheckService.requireSchoolAdmin();
}
@At
@ViReturn
public Object taskData(String taskName, String taskDate, int pageNumber, int pageSize) {
try {
levelCheckService.requireSchoolAdmin();
return Result.success().addData(levelCheckService.schoolTasks(
taskName, taskDate, pageNumber, pageSize));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@ViReturn
public Object unitTaskData(String taskId, String unitName, Integer state, Integer managerState,
int pageNumber, int pageSize) {
try {
levelCheckService.requireSchoolAdmin();
return Result.success().addData(levelCheckService.schoolUnitTasks(
taskId, unitName, state, managerState, pageNumber, pageSize));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@ViReturn
public Object unitDataPage(String unitTaskId, String versionType, String searchName, String searchKeyword,
Integer state,
int pageNumber, int pageSize) {
try {
return Result.success().addData(levelCheckService.unitDataPage(
unitTaskId, versionType, searchName, searchKeyword, state, pageNumber, pageSize));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@ViReturn
public Object currentDataPage(String taskId, String searchName, String searchKeyword, Integer state,
int pageNumber, int pageSize) {
try {
return Result.success().addData(levelCheckService.currentDataPage(
taskId, searchName, searchKeyword, state, pageNumber, pageSize));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@POST
@ViReturn
@Aop(TransAop.READ_COMMITTED)
public Object doDelete(String taskId) {
try {
levelCheckService.deleteTask(taskId);
return null;
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@POST
@ViReturn
@Aop(TransAop.READ_COMMITTED)
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
public Object doUpload(String taskName, String description,
@Param("files") TempFile[] files) {
try {
levelCheckService.requireSchoolAdmin();
NutMap result = levelCheckService.createTask(taskName, description, files);
String message = result.getInt("errorCount") > 0 ? "导入完成,部分数据未分发" : "分发成功";
return Result.success(message).addData(result);
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
public void exportImportErrors(String cacheKey, HttpServletResponse response) {
try {
levelCheckService.requireSchoolAdmin();
byte[] bytes = levelCheckService.getImportErrorFile(cacheKey);
String fileName = "分级核对导入错误.xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename="
+ URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
response.getOutputStream().write(bytes);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
@At
public void downloadTemplate(HttpServletResponse response) {
try {
byte[] bytes = levelCheckService.getImportTemplate();
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename="
+ URLEncoder.encode("分级核对导入模板.xlsx", StandardCharsets.UTF_8.name()));
response.getOutputStream().write(bytes);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
@At
@ViReturn
@Aop(TransAop.READ_COMMITTED)
public Object doAudit(String unitTaskId, boolean approved, String opinion) {
try {
levelCheckService.audit(unitTaskId, approved, opinion);
return null;
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
public void download(String unitTaskId, String versionType, HttpServletResponse response) {
try {
byte[] bytes = levelCheckService.getDownloadBytes(unitTaskId, versionType, true);
String fileName = levelCheckService.getDownloadFileName(unitTaskId, versionType, true);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename="
+ URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
response.getOutputStream().write(bytes);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
@At
public void exportCurrent(String taskId, HttpServletResponse response) {
try {
byte[] bytes = levelCheckService.exportCurrent(taskId);
String fileName = "分级核对当前生效数据.xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename="
+ URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
response.getOutputStream().write(bytes);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
}
@@ -0,0 +1,97 @@
package io.v.nutz.levelcheck.controller;
import cn.wizzer.framework.base.Result;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.levelcheck.service.LevelCheckService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.*;
import org.nutz.mvc.upload.TempFile;
import org.nutz.mvc.upload.UploadAdaptor;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* 单位分级核对
*/
@IocBean
@At("/platform/levelCheck/unit")
@Ok("json:full")
@RequiresAuthentication
public class LevelCheckUnitController {
@Inject
private LevelCheckService levelCheckService;
@At("")
@Ok("beetl:/platform/levelCheck/unit.html")
public void index() {
}
@At
@ViReturn
public Object taskData(String keyword, Integer state, int pageNumber, int pageSize) {
try {
return Result.success().addData(levelCheckService.myUnitTasks(
keyword, state, pageNumber, pageSize));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@ViReturn
@Aop(TransAop.READ_COMMITTED)
public Object doConfirm(String unitTaskId) {
try {
levelCheckService.confirm(unitTaskId);
return null;
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@ViReturn
public Object dataPage(String unitTaskId, String searchName, String searchKeyword, Integer state,
int pageNumber, int pageSize) {
try {
return Result.success().addData(levelCheckService.myUnitDataPage(
unitTaskId, searchName, searchKeyword, state, pageNumber, pageSize));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@POST
@ViReturn
@Aop(TransAop.READ_COMMITTED)
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
public Object doUploadRevision(String unitTaskId, @Param("file") TempFile file) {
try {
levelCheckService.submitRevision(unitTaskId, file);
return null;
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
public void download(String unitTaskId, HttpServletResponse response) {
try {
byte[] bytes = levelCheckService.getDownloadBytes(unitTaskId, "current", false);
String fileName = levelCheckService.getDownloadFileName(unitTaskId, "current", false);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename="
+ URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
response.getOutputStream().write(bytes);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
}
@@ -0,0 +1,49 @@
package io.v.nutz.levelcheck.model;
import io.v.nutz.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("level_check_action_log")
@TableIndexes({@Index(name = "IDX_LEVEL_CHECK_LOG_UNIT", fields = {"unitTaskId"}, unique = false)})
public class LevelCheckActionLog extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unitTaskId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 30)
private String action;
@Column
private Integer fromState;
@Column
private Integer toState;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String operatorId;
@Column
private Long operateAt;
@Column
@ColDefine(type = ColType.VARCHAR, width = 500)
private String opinion;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String fileVersionId;
}
@@ -0,0 +1,54 @@
package io.v.nutz.levelcheck.model;
import io.v.nutz.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
/**
* 分级核对固定模板数据明细
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("level_check_data_row")
@TableIndexes({@Index(name = "IDX_LEVEL_CHECK_DATA_VERSION", fields = {"versionId", "sequenceNo"}, unique = true)})
public class LevelCheckDataRow extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String versionId;
@Column
private Integer sequenceNo;
@Column
@ColDefine(customType = "text")
private String salaryUnitCode;
@Column
@ColDefine(customType = "text")
private String employeeNo;
@Column
@ColDefine(customType = "text")
private String employeeName;
@Column
@ColDefine(customType = "text")
private String unionFee;
@Column
@ColDefine(customType = "text")
private String yearMonth;
@Column
@ColDefine(customType = "text")
private String unitName;
}
@@ -0,0 +1,53 @@
package io.v.nutz.levelcheck.model;
import io.v.nutz.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("level_check_file_version")
@TableIndexes({@Index(name = "IDX_LEVEL_CHECK_FILE_UNIT", fields = {"unitTaskId", "versionNo"}, unique = true)})
public class LevelCheckFileVersion extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unitTaskId;
@Column
private Integer versionNo;
@Column
@ColDefine(type = ColType.VARCHAR, width = 20)
private String versionType;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String sysFileId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 200)
private String originalFileName;
@Column
private Integer rowCount;
@Column
@ColDefine(type = ColType.VARCHAR, width = 64)
private String sha256;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String uploadedBy;
@Column
private Long uploadedAt;
}
@@ -0,0 +1,41 @@
package io.v.nutz.levelcheck.model;
import io.v.nutz.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("level_check_task")
public class LevelCheckTask extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 100)
@Comment("任务名称")
private String name;
@Column
@Comment("任务日期")
private Long taskDate;
@Column
@Comment("总单位数")
private Integer totalUnits;
@Column
@Comment("已完成单位数")
private Integer finishedUnits;
@Column
@ColDefine(type = ColType.VARCHAR, width = 500)
@Comment("任务说明")
private String description;
}
@@ -0,0 +1,85 @@
package io.v.nutz.levelcheck.model;
import io.v.nutz.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@Table("level_check_unit_task")
@TableIndexes({
@Index(name = "IDX_LEVEL_CHECK_UNIT_TASK", fields = {"taskId", "unitId"}, unique = true),
@Index(name = "IDX_LEVEL_CHECK_UNIT_STATE", fields = {"state"}, unique = false)
})
public class LevelCheckUnitTask extends BaseModel {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String taskId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unitId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 100)
private String unitName;
@Column
private Integer unitLevel;
@Column
@ColDefine(type = ColType.VARCHAR, width = 20)
private String managerType;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unionId;
@Column
private Integer state;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String originalVersionId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String currentVersionId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String pendingVersionId;
@Column
private Integer rowCount;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String checkedBy;
@Column
private Long checkedAt;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
private String auditedBy;
@Column
private Long auditedAt;
@Column
@ColDefine(type = ColType.VARCHAR, width = 500)
private String auditOpinion;
@Column
private Integer dataVersion;
}
File diff suppressed because it is too large Load Diff
@@ -49,7 +49,9 @@ public class MemberAgendaController {
final String title = "【会员系统】";
List<NutMap> list = new ArrayList<>();
if (!ShiroUtil.hasAnyRoles("sysadmin,A06,gh01,H03")) {
if (!ShiroUtil.hasAnyRoles("sysadmin,A06,gh01,H03")
&& !org.apache.shiro.SecurityUtils.getSubject().isPermitted("member.apply.audit.union")
&& !org.apache.shiro.SecurityUtils.getSubject().isPermitted("member.apply.audit.school")) {
return list;
}
boolean anyRoles = ShiroUtil.hasAnyRoles("sysadmin,A06,H03");
@@ -67,20 +69,26 @@ public class MemberAgendaController {
int marUnionNum = 0;
Cnd marUnionCnd = Cnd.where("mar.stateId", "=", MemberApplyState.UNION);
if (!anyRoles) {
marUnionCnd.and("u.unionid", "=", Vi.getUnionId());
// 入会审核范围与 PC 部门审核列表一致,校级角色不能自动越过部门权限。
if (!ShiroUtil.hasAnyRoles("sysadmin")) {
marUnionCnd.and("mar.unionId", "=", Vi.getUnionId());
}
marSql.setCondition(marUnionCnd);
marUnionNum = memberService.count(marSql);
list.add(new NutMap().addv("title", title).addv("iconClass", vi.getIconByPath("/platform/member/apply/audit/union")).addv("label", "待部门工会入会审核").addv("number", marUnionNum).addv("url", "/platform/member/apply/audit/union"));
if (org.apache.shiro.SecurityUtils.getSubject().isPermitted("member.apply.audit.union"))
list.add(new NutMap().addv("title", title).addv("iconClass", vi.getIconByPath("/platform/member/apply/audit/union")).addv("label", "待部门工会入会审核").addv("number", marUnionNum).addv("url", "/platform/member/apply/audit/union"));
int marSchoolNum = 0;
Cnd marSchoolCnd = Cnd.where("mar.stateId", "=", MemberApplyState.SCHOOL_UNION);
marSql.setCondition(marSchoolCnd);
marSchoolNum = memberService.count(marSql);
list.add(new NutMap().addv("title", title).addv("iconClass", vi.getIconByPath("/platform/member/apply/audit/school")).addv("label", "待校工会入会审核").addv("number", marSchoolNum).addv("url", "/platform/member/apply/audit/school"));
if (org.apache.shiro.SecurityUtils.getSubject().isPermitted("member.apply.audit.school"))
list.add(new NutMap().addv("title", title).addv("iconClass", vi.getIconByPath("/platform/member/apply/audit/school")).addv("label", "待校工会入会审核").addv("number", marSchoolNum).addv("url", "/platform/member/apply/audit/school"));
// 非原会员变更管理角色仅返回按权限计算的入会待办。
if (!ShiroUtil.hasAnyRoles("sysadmin,A06,gh01,H03")) return list;
// 会员变更
Sql mcrSql = Sqls.create("""
SELECT
@@ -111,4 +119,24 @@ public class MemberAgendaController {
List<Sys_menu> menus = Optional.ofNullable(user).orElse(new Sys_user()).getMenus();
return list.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").contains(x.getHref()))).distinct().collect(Collectors.toList());
}
/**
* 无需参数,返回当前登录人今年可办理的入会审核节点。
* 每项 title/name/count/mobileHref 分别表示业务标题、节点名称、待办数量和手机入口。
*/
@At
@ViReturn
public Object getMobileAgenda() {
return memberService.getCurrentYearApplyAgenda();
}
/**
* 无需传入办理人,身份从登录会话获取;返回本人今年实际办理的会员入会记录。
* 每项 moduleName/auditState/auditTime/mobileUrl 为业务名称、办理节点、办理时间和只读详情地址。
*/
@At
@ViReturn
public Object getCompletes() {
return memberService.getCurrentYearApplyCompletes();
}
}
@@ -15,6 +15,8 @@ import io.v.nutz.util.ShiroUtil;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Sqls;
import org.nutz.ioc.aop.Aop;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -130,6 +132,7 @@ public class MemberApplyAuditController {
@At
@ViReturn
@Aop(TransAop.READ_COMMITTED)
public Object doAudit(@ViRequired String recordId, Audit audit) {
MemberApplyRecord record = memberApplyRecordViService.fetch(recordId);
MemberApplyAuditHandle applyHandle = Enum.instance(MemberApplyAuditHandle.class, record.getStateId());
@@ -1,18 +1,28 @@
package io.v.nutz.member.controller.inquire;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.member.service.MemberService;
import io.v.nutz.modules.models.PageForm;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.entity.Record;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
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 javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -47,7 +57,7 @@ public class MemberDataBoardController {
@ViReturn
public Object memberNumber() {
int memberNum = memberService.count(Sqls.create("select count(1) from member"));
int lastYearMemberNum = memberService.count(Sqls.create("select * from member_his where year = year(now()) -1 "));
int lastYearMemberNum = memberService.count(Sqls.create("select * from member_his where year = year(now()) -1 and member = 1 "));
return Map.of("memberNum", memberNum, "lastYearMemberNum", lastYearMemberNum);
}
@@ -60,7 +70,7 @@ public class MemberDataBoardController {
List<Map> result = new ArrayList<>();
Integer year = DateUtil.getYear();
for (int i = year - 5; i < year; i++) {
int count = memberService.count(Sqls.create("select * from member_his where year = @year ").setParam("year", year));
int count = memberService.count(Sqls.create("select * from member_his where year = @year and member = 1 ").setParam("year", i));
result.add(Map.of("label", i, "value", count));
}
result.add(Map.of("label", year, "value", memberService.count(Sqls.create("select count(1) from member"))));
@@ -104,6 +114,7 @@ public class MemberDataBoardController {
SELECT
un.unionname,
un.unioncode,
un.id unionid,
( SELECT count( 1 ) FROM member WHERE unionid = un.id ) `value`
FROM
sys_union un
@@ -166,5 +177,122 @@ public class MemberDataBoardController {
return memberService.list(sql);
}
/**
* 看板指标下钻明细
*/
@At
@ViReturn
@RequiresPermissions("member.inquire.board")
public Object detailPage(PageForm pageForm, Integer year, String unionId, String unitId,
String personType, String userState, String sex, String searchKeyword,
String startTime, String endTime) {
return memberService.list(pageForm, buildDetailSql(year, unionId, unitId, personType,
userState, sex, searchKeyword, startTime, endTime));
}
/**
* 导出当前筛选条件下的全部下钻明细
*/
@At
@Ok("void")
@RequiresPermissions("member.inquire.board")
public void exportDetail(HttpServletResponse response, Integer year, String unionId, String unitId,
String personType, String userState, String sex, String searchKeyword,
String startTime, String endTime) throws IOException {
List<Record> list = memberService.list(buildDetailSql(year, unionId, unitId, personType,
userState, sex, searchKeyword, startTime, endTime));
List<ExcelExportEntity> columns = new ArrayList<>();
columns.add(new ExcelExportEntity("工号", "loginname", 18));
columns.add(new ExcelExportEntity("姓名", "username", 16));
columns.add(new ExcelExportEntity("性别", "sex", 10));
columns.add(new ExcelExportEntity("人员类型", "personType", 18));
columns.add(new ExcelExportEntity("在职状态", "userState", 16));
columns.add(new ExcelExportEntity("所属工会", "unionname", 25));
columns.add(new ExcelExportEntity("所属单位", "unitname", 25));
columns.add(new ExcelExportEntity("入会时间", "detailTime", 18));
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename="
+ new String("会员看板明细.xls".getBytes("UTF-8"), "ISO8859-1"));
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), columns, list);
workbook.write(response.getOutputStream());
workbook.close();
}
/**
* 明细弹窗附加选项,会员看板暂无业务状态选项
*/
@At
@ViReturn
@RequiresPermissions("member.inquire.board")
public Object detailOptions() {
return Map.of("medicalStates", List.of());
}
private Sql buildDetailSql(Integer year, String unionId, String unitId, String personType,
String userState, String sex, String searchKeyword,
String startTime, String endTime) {
int currentYear = DateUtil.getYear();
boolean history = year != null && year != currentYear;
String timeField = "h.memberJoinTime";
Sql sql = Sqls.create("""
SELECT
m.id,
m.loginname,
m.username,
m.sex,
m.personType,
m.userState,
m.unionid,
m.unionname,
m.unitid,
m.unitname,
$detailTime detailTime
FROM $table m
$detailJoin
$condition
""")
.setVar("table", history ? "member_his" : "`user`")
.setVar("detailTime", timeField)
.setVar("detailJoin", history
? "LEFT JOIN member_history h ON h.id = m.id AND h.year = m.year"
: "LEFT JOIN sys_user h ON h.id = m.id");
Cnd cnd = Cnd.NEW();
cnd.and("m.member", "=", 1);
if (history) {
cnd.and("m.year", "=", year);
}
if (Strings.isNotBlank(unionId)) {
cnd.and("m.unionid", "=", unionId);
}
if (Strings.isNotBlank(unitId)) {
cnd.and("m.unitid", "=", unitId);
}
if (Strings.isNotBlank(personType)) {
cnd.and("m.personType", "=", personType);
}
if (Strings.isNotBlank(userState)) {
cnd.and("m.userState", "=", userState);
}
if (Strings.isNotBlank(sex)) {
cnd.and("m.sex", "=", sex);
}
if (Strings.isNotBlank(startTime)) {
cnd.and(timeField, ">=", startTime);
}
if (Strings.isNotBlank(endTime)) {
cnd.and(timeField, "<=", endTime + " 23:59:59");
}
if (Strings.isNotBlank(searchKeyword)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("m.username", searchKeyword);
group.orLike("m.loginname", searchKeyword);
cnd.and(group);
}
cnd.asc("m.unioncode").asc("m.loginname");
sql.setCondition(cnd);
return sql;
}
}
@@ -7,6 +7,7 @@ import io.v.nutz.diseaseaid.constant.AidFundMemberAuditState;
import io.v.nutz.diseaseaid.mode.AidFundMemberMode;
import io.v.nutz.diseaseaid.model.AidFundMemberAuditRecord;
import io.v.nutz.member.UserMode;
import io.v.nutz.member.service.MemberService;
import io.v.nutz.member.constant.MemberApplyState;
import io.v.nutz.member.constant.MemberOrigin;
import io.v.nutz.member.model.MemberApplyRecord;
@@ -25,6 +26,8 @@ import io.v.nutz.util.ShiroUtil;
import io.v.nutz.util.ViResource;
import io.v.nutz.web.commons.base.Globals;
import lombok.Getter;
import org.nutz.ioc.aop.Aop;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
@@ -55,6 +58,7 @@ public enum MemberApplyAuditHandle {
*/
CREATE(null) {
@Override
@Aop(TransAop.READ_COMMITTED)
public MemberApplyRecord next(MemberApplyRecord record, Audit audit) {
record.setUserId(ShiroUtil.getPrincipalProperty("id").toString());
record.setStateId(MemberApplyState.UNION);
@@ -64,22 +68,8 @@ public enum MemberApplyAuditHandle {
Sys_user user = sysUserService.fetch(record.getUserId());
Sql sql = Sqls.create("""
SELECT
su.loginname,
su.username
FROM
sys_user_role sur
LEFT JOIN sys_user su ON su.id = sur.userId
WHERE
sur.roleId = @roleId
AND sur.unionid = @unionId
""")
// .setParam("roleId", Roles.UNION_MANGER)
.setParam("roleId", Roles.GH01)
.setParam("unionId", record.getUnionId());
List<NutMap> mapList = sysUserService.listMap(sql);
// 接收人由 PC 部门审核权限关联的角色确定,并限定申请所属工会。
List<NutMap> mapList = memberService.applyAuditRecipients(false, record.getUnionId());
if (Lang.isNotEmpty(mapList)) {
List<TodoTask.Processor> processors = new ArrayList<>();
@@ -102,6 +92,8 @@ public enum MemberApplyAuditHandle {
todoTask.setInitiatorId(user.getLoginname());
todoTask.setPcViewUrl(Globals.AppDomain + "/platform/member/apply/audit/union");
todoTask.setH5ViewUrl(Globals.AppDomain + "/mobile/member/audit");
todoTask.setDoneH5Url(Globals.AppDomain + "/mobile/member/audit?isAudit=true");
todoTask.setDonePcUrl(todoTask.getPcViewUrl() + "?audit=1");
todoTask.setBizId(insert.getId());
todoTask.setBizType("申请入会部门工会审核");
todoTask.setStatus(TodoTaskEnum.TaskStatus.ACTIVE.name());
@@ -121,6 +113,7 @@ public enum MemberApplyAuditHandle {
*/
AUDIT_UNION(MemberApplyState.UNION) {
@Override
@Aop(TransAop.READ_COMMITTED)
public MemberApplyRecord next(MemberApplyRecord record, Audit audit) {
record.setStateId(audit.getAuditPass() ? MemberApplyState.SCHOOL_UNION : MemberApplyState.UNION_FAIL);
@@ -130,18 +123,8 @@ public enum MemberApplyAuditHandle {
if (audit.getAuditPass()) {
Sql sql = Sqls.create("""
SELECT
su.loginname,
su.username
FROM
sys_user_role sur
LEFT JOIN sys_user su ON su.id = sur.userId
WHERE
sur.roleId = @roleId
""").setParam("roleId", Roles.H03);
List<NutMap> mapList = sysUserService.listMap(sql);
// 部门通过后仅向具有 PC 校工会审核权限的角色成员派发下一阶段待办。
List<NutMap> mapList = memberService.applyAuditRecipients(true, record.getUnionId());
if (Lang.isNotEmpty(mapList)) {
List<TodoTask.Processor> processors = new ArrayList<>();
@@ -163,6 +146,8 @@ public enum MemberApplyAuditHandle {
todoTask.setInitiatorId(user.getLoginname());
todoTask.setPcViewUrl(Globals.AppDomain + "/platform/member/apply/audit/school");
todoTask.setH5ViewUrl(Globals.AppDomain + "/mobile/member/audit");
todoTask.setDoneH5Url(Globals.AppDomain + "/mobile/member/audit?isAudit=true");
todoTask.setDonePcUrl(todoTask.getPcViewUrl() + "?audit=1");
todoTask.setBizId(record.getId());
todoTask.setBizType("申请入会校工会审核");
todoTask.setStatus(TodoTaskEnum.TaskStatus.ACTIVE.name());
@@ -178,14 +163,7 @@ public enum MemberApplyAuditHandle {
List.of(Map.of("tool", MsgApi.sendTool.wechat_enterprise.name())));
}
//部门工会的待办设置为完成
TodoTask todoTask = dao.fetch(TodoTask.class, Cnd.where("bizId", "=", record.getId())
.and("bizType", "=", "申请入会部门工会审核"));
if (todoTask != null) {
todoTask.setProcessorId(ShiroUtil.getPrincipalProperty("loginname").toString());
todoTask.setStatus(TodoTaskEnum.TaskStatus.COMPLETE.name());
todoTaskInterface.updateTask(todoTask);
}
memberService.completeApplyAuditTasks(record.getId(), false);
return record;
}
},
@@ -194,6 +172,7 @@ public enum MemberApplyAuditHandle {
*/
AUDIT_SCHOOL(MemberApplyState.SCHOOL_UNION) {
@Override
@Aop(TransAop.READ_COMMITTED)
public MemberApplyRecord next(MemberApplyRecord record, Audit audit) {
memberApplyRecordViService.transactional(() -> {
@@ -242,14 +221,7 @@ public enum MemberApplyAuditHandle {
List.of(Map.of("tool", MsgApi.sendTool.wechat_enterprise.name())));
}
//校工会的待办设置为完成
TodoTask todoTask = dao.fetch(TodoTask.class, Cnd.where("bizId", "=", record.getId())
.and("bizType", "=", "申请入会校工会审核"));
if (todoTask != null) {
todoTask.setProcessorId(ShiroUtil.getPrincipalProperty("loginname").toString());
todoTask.setStatus(TodoTaskEnum.TaskStatus.COMPLETE.name());
todoTaskInterface.updateTask(todoTask);
}
memberService.completeApplyAuditTasks(record.getId(), true);
});
sysUserService.deleteCache(record.getUserId());
@@ -270,6 +242,7 @@ public enum MemberApplyAuditHandle {
public TodoTaskInterface todoTaskInterface = ViResource.ioc.getByType(TodoTaskInterface.class);
public MsgApi msgApi = ViResource.ioc.getByType(MsgApi.class);
public Dao dao = ViResource.dao;
public MemberService memberService = ViResource.ioc.getByType(MemberService.class);
public abstract MemberApplyRecord next(MemberApplyRecord record, Audit audit);
@@ -34,4 +34,21 @@ public interface MemberService extends ViService<Sys_user> {
* @return
*/
Pagination memberChangeRecords(PageForm pageForm, CndPlus cnd);
/** 按当前用户 PC 审核权限查询手机列表;isAudit 为已审标记,year 可空,返回 list/totalCount 分页数据。 */
Object mobileApplyAuditPage(int pageNumber, int pageSize, boolean isAudit, Integer year);
/** 按申请记录校验访问范围并返回审核/只读视图,userId 必须属于该记录,done 强制只读。 */
String mobileApplyAuditView(String recordId, String userId, boolean done);
/** 查询拥有指定阶段审核权限的启用角色成员;部门阶段限定 unionId,返回 loginname/username。 */
List<NutMap> applyAuditRecipients(boolean school, String unionId);
/** 关闭指定申请当前阶段的全部有效待办,保留接收人并记录实际办理人和已办地址。 */
void completeApplyAuditTasks(String recordId, boolean school);
/** 返回当前年份入会待办卡片;身份取当前会话,部门限定本工会,超管可查看两级范围。 */
List<NutMap> getCurrentYearApplyAgenda();
/** 返回当前登录人本年度实际审核的入会记录及只读详情地址,不把角色范围内的已审视作本人已办。 */
List<NutMap> getCurrentYearApplyCompletes();
}
@@ -15,6 +15,21 @@ import org.nutz.lang.util.NutMap;
import org.nutz.plugins.wkcache.annotation.CacheDefaults;
import java.util.List;
import java.util.Objects;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.member.constant.MemberApplyState;
import io.v.nutz.member.model.MemberApplyRecord;
import io.v.nutz.todoTask.TodoTask;
import io.v.nutz.todoTask.TodoTaskInterface;
import io.v.nutz.web.commons.base.Globals;
import io.v.nutz.web.commons.utils.ShiroUtil;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.permission.WildcardPermission;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.aop.Aop;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.lang.Strings;
/**
@@ -70,4 +85,151 @@ public class MemberServiceImpl extends ViServiceImpl<Sys_user> implements Member
Sql sql = Sqls.create(MEMBER_CHANGE_RECORDS).setCondition(cnd);
return list(pageForm, sql);
}
@Inject
private TodoTaskInterface todoTaskInterface;
/** 与 PC 页面的权限注解一致,不能用待办角色名称替代审核权限。 */
private boolean canApplyAudit(boolean school) {
return ShiroUtil.hasPermission("member.apply.audit." + (school ? "school" : "union"));
}
@Override
public Object mobileApplyAuditPage(int pageNumber, int pageSize, boolean isAudit, Integer year) {
boolean union = canApplyAudit(false);
boolean school = canApplyAudit(true);
if (!union && !school) throw new UnauthorizedException("无会员审核权限");
Sql sql = Sqls.create("SELECT mar.*, st.stateName, st.stateColor, u.unionname, u.username, "
+ "u.loginname, u.personType FROM member_apply_record mar "
+ "LEFT JOIN audit_state st ON st.stateId=mar.stateId LEFT JOIN `user` u ON u.id=mar.userId $condition");
SqlExpressionGroup scope = new SqlExpressionGroup();
// 已审沿用 PC 各阶段 stateId > 当前阶段的口径;双权限取并集,避免重复记录。
if (union && (ShiroUtil.hasRole("sysadmin") || Strings.isNotBlank(Vi.getUnionId()))) {
Cnd department = Cnd.where("mar.stateId", isAudit ? ">" : "=", MemberApplyState.UNION);
if (!ShiroUtil.hasRole("sysadmin")) department.and("mar.unionId", "=", Vi.getUnionId());
scope.or(department.where());
} else if (!school) {
return listPageMap(Math.max(1, pageNumber), Math.max(1, pageSize), sql.setCondition(Cnd.where("1", "=", 0)));
}
if (school) scope.or("mar.stateId", isAudit ? ">" : "=", MemberApplyState.SCHOOL_UNION);
CndPlus cnd = CndPlus.create();
cnd.and(scope);
cnd.andExcludeEmpty("YEAR(mar.applyTime)", "=", year);
cnd.asc("mar.stateId").desc("mar.applyTime");
return listPageMap(Math.max(1, pageNumber), Math.max(1, Math.min(pageSize, 100)), sql.setCondition(cnd));
}
@Override
public String mobileApplyAuditView(String recordId, String userId, boolean done) {
MemberApplyRecord record = Strings.isBlank(recordId) ? null : dao().fetch(MemberApplyRecord.class, recordId);
if (record == null || !Objects.equals(record.getUserId(), userId)) {
throw new IllegalArgumentException("申请记录不存在或申请人不匹配");
}
boolean union = canApplyAudit(false) && (ShiroUtil.hasRole("sysadmin")
|| Strings.isNotBlank(Vi.getUnionId()) && Objects.equals(Vi.getUnionId(), record.getUnionId()));
boolean school = canApplyAudit(true) && record.getStateId() != null
&& record.getStateId() >= MemberApplyState.SCHOOL_UNION;
if (!union && !school) throw new UnauthorizedException("无权查看该申请");
if (!done && union && Objects.equals(record.getStateId(), MemberApplyState.UNION)) {
return "beetl:/mobile/member/union_Audit.html";
}
if (!done && school && Objects.equals(record.getStateId(), MemberApplyState.SCHOOL_UNION)) {
return "beetl:/mobile/member/school_Audit.html";
}
return "beetl:/mobile/member/detail.html";
}
@Override
public List<NutMap> applyAuditRecipients(boolean school, String unionId) {
// 从 PC 权限对应的角色成员取接收人,兼容 Shiro 通配权限并排除禁用角色、菜单和账号。
Sql sql = Sqls.create("SELECT DISTINCT su.loginname, su.username, sm.permission FROM sys_user_role sur "
+ "JOIN sys_user su ON su.id=sur.userId JOIN sys_role sr ON sr.id=sur.roleId "
+ "JOIN sys_role_menu srm ON srm.roleId=sr.id JOIN sys_menu sm ON sm.id=srm.menuId "
+ "WHERE sr.disabled=false AND sm.disabled=false AND su.disabled=false "
+ (school ? "" : "AND sur.unionid=@unionId AND su.unionid=@unionId "));
sql.setParam("unionId", unionId);
WildcardPermission required = new WildcardPermission("member.apply.audit." + (school ? "school" : "union"));
java.util.Map<String, NutMap> recipients = new java.util.LinkedHashMap<>();
for (NutMap row : listMap(sql)) {
String permission = row.getString("permission");
if (Strings.isNotBlank(permission) && Strings.isNotBlank(row.getString("loginname"))
&& new WildcardPermission(permission).implies(required)) {
recipients.putIfAbsent(row.getString("loginname"), row);
}
}
return new java.util.ArrayList<>(recipients.values());
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void completeApplyAuditTasks(String recordId, boolean school) {
// 同阶段可能存在历史重复待办,只关闭 ACTIVE 项,保留接收角色成员列表用于已办归属。
List<TodoTask> tasks = dao().query(TodoTask.class, Cnd.where("bizId", "=", recordId)
.and("bizType", "=", school ? "申请入会校工会审核" : "申请入会部门工会审核")
.and("status", "=", "ACTIVE").and(Cnd.exps("delFlag", "=", false).or("delFlag", "is", null)));
for (TodoTask task : tasks) {
task.setProcessorId(ShiroUtil.getPrincipalProperty("loginname").toString());
task.setStatus("COMPLETE");
task.setDonePcUrl(Globals.AppDomain + "/platform/member/apply/audit/" + (school ? "school" : "union") + "?audit=1");
task.setDoneH5Url(Globals.AppDomain + "/mobile/member/audit?isAudit=true");
todoTaskInterface.updateTask(task);
}
}
@Override
public List<NutMap> getCurrentYearApplyAgenda() {
int year = java.time.LocalDate.now().getYear();
boolean admin = ShiroUtil.hasRole("sysadmin");
List<NutMap> items = new java.util.ArrayList<>();
for (boolean school : new boolean[]{false, true}) {
if (!admin && !canApplyAudit(school)) continue;
// 没有工会归属的部门审核人不能因空条件而获得全校范围。
if (!school && !admin && Strings.isBlank(Vi.getUnionId())) continue;
Cnd cnd = Cnd.where("stateId", "=", school ? MemberApplyState.SCHOOL_UNION : MemberApplyState.UNION)
.and("applyTime", ">=", year + "-01-01")
.and("applyTime", "<", (year + 1) + "-01-01");
if (!school && !admin) cnd.and("unionId", "=", Vi.getUnionId());
int count = dao().count(MemberApplyRecord.class, cnd);
items.add(new NutMap().setv("title", "【会员入会】")
.setv("name", school ? "待校工会入会审核" : "待部门工会入会审核")
.setv("count", count).setv("mobileHref", "/mobile/member/audit"));
}
return items;
}
@Override
public List<NutMap> getCurrentYearApplyCompletes() {
Object principalLoginname = ShiroUtil.getPrincipalProperty("loginname");
String loginname = principalLoginname == null ? "" : principalLoginname.toString();
boolean admin = ShiroUtil.hasRole("sysadmin");
boolean union = admin || canApplyAudit(false);
boolean school = admin || canApplyAudit(true);
if (Strings.isBlank(loginname) || !union && !school) return java.util.Collections.emptyList();
int year = java.time.LocalDate.now().getYear();
// 只读取当前申请实际关联的审核记录,按办理工号和办理年份筛选;同人办理两级保留两个节点。
Sql sql = Sqls.create("""
SELECT mar.id, a.id auditId, nodes.nodeId,
CONCAT('【会员入会】', COALESCE(u.username, '')) moduleName,
nodes.nodeName auditState, a.auditTime,
CONCAT('/mobile/member/audit/findOne?record_id=', mar.id,
'&user_id=', mar.userId, '&done=true') mobileUrl
FROM member_apply_record mar
LEFT JOIN `user` u ON u.id=mar.userId
JOIN (SELECT 1 nodeId, '部门工会审核' nodeName
UNION ALL SELECT 2, '校工会审核') nodes ON 1=1
JOIN audit a ON a.id=CASE nodes.nodeId WHEN 1 THEN mar.unionAuditId ELSE mar.schoolAuditId END
WHERE a.loginname=@loginname AND a.auditTime>=@startTime AND a.auditTime<@endTime
AND ((nodes.nodeId=1 AND @unionAllowed=1 AND (@admin=1 OR mar.unionId=@unionId))
OR (nodes.nodeId=2 AND @schoolAllowed=1))
ORDER BY a.auditTime DESC, mar.id, nodes.nodeId
""");
sql.setParam("loginname", loginname);
sql.setParam("startTime", year + "-01-01");
sql.setParam("endTime", (year + 1) + "-01-01");
sql.setParam("unionAllowed", union ? 1 : 0);
sql.setParam("schoolAllowed", school ? 1 : 0);
sql.setParam("admin", admin ? 1 : 0);
sql.setParam("unionId", Strings.sNull(Vi.getUnionId()));
return listMap(sql);
}
}
@@ -5,21 +5,19 @@ import cn.wizzer.framework.base.Result;
import io.v.nutz.activity.models.ActivityTissue;
import io.v.nutz.activity.models.ActivityTissuePerson;
import io.v.nutz.activity.models.ActivityUserScope;
import io.v.nutz.activity.utils.ActivityRoleUtil;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.service.SimpleService;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.modules.models.PageForm;
import io.v.nutz.sys.models.Sys_user;
import io.v.nutz.sys.models.User;
import io.v.nutz.web.commons.utils.ShiroUtil;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.FieldFilter;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -158,7 +156,7 @@ public class UnionActivityRegisterController {
allCnd.andEX("signUpMethod", "is not", null);
allCnd.and(new Static("(SELECT COUNT(1) FROM activity_user_scope WHERE groupId= ti.groupId AND userId='%s') >0".formatted(io.v.nutz.util.ShiroUtil.getUserId())));
if (!ShiroUtil.hasAnyRoles(Lang.list("sysadmin", "A06"))) {
if (ShiroUtil.hasRole("H04")) {
if (ActivityRoleUtil.isUnionManager()) {
allCnd.andEX("signUpMethod", "in", Lang.array(1, 2));
} else {
allCnd.andEX("signUpMethod", "=", 1);
@@ -272,10 +270,17 @@ public class UnionActivityRegisterController {
@ViReturn
@RequiresAuthentication
public Object selectRegisterList(String activityId) {
List list = simpleService.listMap(Sqls.create("select atp.userId AS id,atp.userName,us.unionid from activity_tissue_person atp LEFT JOIN `user` us ON us.id=atp.userId where tissueId = @activityId and us.unionid=@unionid").setParam("activityId", activityId).setParam("unionid", Vi.getUnionId()));
List list = simpleService.listMap(Sqls.create("select atp.userId AS id,atp.userName,un.unionid from activity_tissue_person atp LEFT JOIN `user` us ON us.id=atp.userId LEFT JOIN activity_basic_unit un ON un.id=us.unitid where tissueId = @activityId and un.unionid=@unionid").setParam("activityId", activityId).setParam("unionid", getActivityUnionId()));
return list;
}
/**
* 获取当前用户所在单位对应的活动联合工会
*/
private String getActivityUnionId() {
return ActivityRoleUtil.getCurrentUnionId(simpleService.dao());
}
/**
* 查询没报名的人
*
@@ -287,7 +292,7 @@ public class UnionActivityRegisterController {
@ViReturn
@RequiresAuthentication
public Object searchNoRegisterUser(String searchKey, String activityId) {
if (!ShiroUtil.hasRole("H04")) {
if (!ActivityRoleUtil.isUnionManager()) {
return Result.error().addMsg("请使用部门工会管理员报名!");
}
@@ -301,23 +306,27 @@ public class UnionActivityRegisterController {
sex,
unitname AS unitName
FROM
`user`
`user` u
LEFT JOIN activity_basic_unit un ON un.id = u.unitid
$condition
""");
String unionId = Vi.getUnionId();
""");
String unionId = getActivityUnionId();
if (Strings.isBlank(unionId)) {
return Result.error().addMsg("未找到您管理的活动联合工会!");
}
Cnd cnd = Cnd.NEW();
cnd.andEX("unionId", "=", unionId);
cnd.andEX("un.unionId", "=", unionId);
// cnd.and(Cnd.likeEX("username", searchKey.trim()));
cnd.and(Cnd.exps(Cnd.likeEX("username", searchKey.trim())).or(Cnd.likeEX("loginname", searchKey.trim())));
cnd.and(new Static("""
id not in (SELECT userid FROM activity_tissue_person WHERE tissueId = '%s')
u.id not in (SELECT userid FROM activity_tissue_person WHERE tissueId = '%s')
""".formatted(activityId)));
// AND id IN
// ( SELECT userid FROM activity_user_scope WHERE groupId IN ( SELECT groupId FROM activity_tissue WHERE id = @activityId AND groupId IS NOT NULL ) )
if (tissue.getGroupId() != null) {
cnd.and(new Static("""
id IN ( SELECT userid FROM activity_user_scope WHERE groupId IN ( SELECT groupId FROM activity_tissue WHERE id = '%s' AND groupId IS NOT NULL ) )
u.id IN ( SELECT userid FROM activity_user_scope WHERE groupId IN ( SELECT groupId FROM activity_tissue WHERE id = '%s' AND groupId IS NOT NULL ) )
""".formatted(activityId)));
}
@@ -348,10 +357,13 @@ public class UnionActivityRegisterController {
@ViReturn
@RequiresAuthentication
public synchronized Object doSaveUnionRegister(String activityId, String[] personIds) {
if (!ShiroUtil.hasRole("H04")) {
if (!ActivityRoleUtil.isUnionManager()) {
return Result.error().addMsg("请使用部门工会管理员报名!");
}
String unionId = Vi.getUnionId();
String unionId = getActivityUnionId();
if (Strings.isBlank(unionId)) {
return Result.error().addMsg("未找到您管理的活动联合工会!");
}
ActivityTissue activityTissue = simpleService.dao().fetch(ActivityTissue.class, activityId);
@@ -371,9 +383,11 @@ public class UnionActivityRegisterController {
}
FieldFilter fieldFilter = FieldFilter.create(User.class, "^id$");
List<String> idList = Daos.ext(simpleService.dao(), fieldFilter).query(User.class, Cnd.where("unionid", "=", unionId))
.stream().map(User::getId).collect(Collectors.toList());
Sql unionUserSql = Sqls.create("SELECT u.id FROM `user` u LEFT JOIN activity_basic_unit un ON un.id = u.unitid WHERE un.unionid = @unionId")
.setParam("unionId", unionId);
List<NutMap> unionUsers = simpleService.listMap(unionUserSql);
List<String> idList = unionUsers.stream()
.map(v -> v.getString("id")).collect(Collectors.toList());
//删除本公会报名的人然后再加
@@ -1,112 +1,47 @@
package io.v.nutz.mobile.member;
import cn.wizzer.framework.base.service.BaseService;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.dao.CndPlus;
import io.v.nutz.member.constant.MemberApplyState;
import io.v.nutz.util.ShiroUtil;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import io.v.nutz.member.service.MemberService;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.Logical;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
/**
* @author zhf
* @date 2021/9/26 9:24
* @description
*/
/** 手机端会员审核统一入口,与 PC 端共享两级审核权限。 */
@IocBean
@At("/mobile/member/audit")
@Ok("json:full")
@RequiresAuthentication
@RequiresPermissions(value = {"member.apply.audit.union", "member.apply.audit.school"}, logical = Logical.OR)
public class MemberAuditUnionController {
@Inject
private MemberService memberService;
@At("")
@Ok("beetl:/mobile/member/audit_list.html")
public void indext() {
public void index() {
}
@Inject
private BaseService baseService;
/**
* pageNumber/pageSize 为页码和每页条数,isAudit 区分已审/待审,year 为申请年份(可空)。
* 返回分页对象,list 为申请记录,totalCount 为符合当前审核权限的数据总数。
*/
@At
@ViReturn
public Object pageData(int pageNumber, int pageSize, boolean isAudit, Integer year) {
Sql sql = Sqls.create("""
SELECT
mar.*,
st.stateName,
st.stateColor,
u.unionname,
u.username,
u.loginname,
u.personType
FROM
`member_apply_record` mar
LEFT JOIN audit_state st ON st.stateId = mar.stateId
LEFT JOIN `user` u ON u.id = mar.userId
$condition
""");
CndPlus cnd = CndPlus.create();
cnd.and("mar.memberStates", "=", 1);
if (ShiroUtil.hasAnyRoles("sysadmin")) {
cnd.and("mar.stateId", "in", isAudit ? Lang.array(MemberApplyState.UNION_FAIL, MemberApplyState.SCHOOL_UNION_FAIL, MemberApplyState.NORMAL_MEMBER) : Lang.array(MemberApplyState.UNION, MemberApplyState.SCHOOL_UNION));
} else {
if (ShiroUtil.hasAnyRoles("A06,wyh02") && ShiroUtil.hasAnyRoles("H04")) {
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.or("mar.stateId", isAudit ? ">" : "=", MemberApplyState.SCHOOL_UNION);
seg.or(CndPlus.exps("mar.unionId", "=", Vi.getUnionId()).and("mar.stateId", isAudit ? ">" : "=", MemberApplyState.UNION));
cnd.and(seg);
} else if (ShiroUtil.hasAnyRoles("A06,wyh02")) {
cnd.and("mar.stateId", isAudit ? ">" : "=", MemberApplyState.SCHOOL_UNION);
} else if (ShiroUtil.hasAnyRoles("H04")) {
cnd.and("mar.unionId", "=", Vi.getUnionId());
cnd.and("mar.stateId", isAudit ? ">" : "=", MemberApplyState.UNION);
}
/*if (ShiroUtil.hasAnyRoles("A06,wyh02")) {
cnd.and("mar.stateId", isAudit ? ">" : "=", MemberApplyState.SCHOOL_UNION);
} else if (ShiroUtil.hasAnyRoles("H04")) {
cnd.and("mar.unionId", "=", Vi.getUnionId());
cnd.and("mar.stateId", isAudit ? ">" : "=", MemberApplyState.UNION);
}*/
}
/* if (!ShiroUtil.hasAnyRoles("sysadmin")) {
if (ShiroUtil.hasAnyRoles("A06,wyh02")) {
cnd.and("mar.stateId", isAudit ? ">" : "=", MemberApplyState.SCHOOL_UNION);
}
}*/
cnd.andExcludeEmpty("YEAR(mar.applyTime)", "=", year);
cnd.asc("mar.stateId");
cnd.desc("mar.applyTime");
sql.setCondition(cnd);
return baseService.listPageMap(pageNumber, pageSize, sql);
return memberService.mobileApplyAuditPage(pageNumber, pageSize, isAudit, year);
}
/**
* record_id 为申请记录 ID,user_id 为申请人 ID,done 表示从已办入口只读查看。
* 返回 Beetl 页面视图;实际审核阶段和访问范围均由数据库记录决定。
*/
@At("/findOne")
@Ok("re")
public String findOne(Integer stateId) {
if (stateId == MemberApplyState.UNION && ShiroUtil.hasAnyRoles("H04,A06,sysadmin")) {
return "beetl:/mobile/member/union_Audit.html";
} else if (stateId == MemberApplyState.SCHOOL_UNION && ShiroUtil.hasAnyRoles("A06,sysadmin")) {
return "beetl:/mobile/member/school_Audit.html";
} /*else if (stateId == MemberApplyState.NORMAL_MEMBER || stateId == MemberApplyState.UNION_FAIL || stateId == MemberApplyState.SCHOOL_UNION_FAIL) {
return "beetl:/mobile/member/detail.html";
}*/
return "beetl:/mobile/member/detail.html";
public String findOne(String record_id, String user_id, boolean done) {
return memberService.mobileApplyAuditView(record_id, user_id, done);
}
}
@@ -121,11 +121,12 @@ public class MemberMobileController {
@ViReturn
@RequiresRoles(value = {"H04", "A06"}, logical = Logical.OR)
public Object viewInfo(String id) {
// 用户表仅存储出生日期,按当前日期计算周岁,空出生日期返回空年龄。
Sql sql = Sqls.create("""
SELECT
u.username,
u.sex,
u.age,
TIMESTAMPDIFF(YEAR, NULLIF(u.birthday, ''), CURDATE()) AS age,
u.nation,
u.education,
u.political,
@@ -32,6 +32,8 @@ import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -140,11 +142,19 @@ public class proposalExportCaseController {
@Ok("void")
@RequiresAuthentication
public void exportAllProposal(String props, PageForm page, ProposalSearch search, String stateCode, String[] proposalTypes, String[] proposalResult,
String searchName2, String searchKeyword2, HttpServletResponse response) throws Exception {
String searchName2, String searchKeyword2,
@Param("undertakeId") String undertakeId,
String[] determineTypeOptions,
String exportCondition,
String exportFileCondition,
HttpServletResponse response) throws Exception {
Cnd cnd = Cnd.NEW();
Sql sql = Sqls.create(SQL);
if (StrUtil.isNotBlank(undertakeId)) {
cnd.and(Cnd.exps("zbpr.replyUnitId", "=", undertakeId).or("xbpr.replyUnitId", "=", undertakeId));
}
cnd.andEX("info.stateCode", "=", stateCode);
cnd.and("info.stateCode", ">", ProposalState.CASE);
if (!ShiroUtil.hasAnyRoles(new String[]{"sysadmin", "A06", "tamange", "jdhdbt01"})) {
@@ -160,6 +170,11 @@ public class proposalExportCaseController {
}
if (proposalResult != null && proposalResult.length > 0) {
cnd.and("info.resultCode", "in", proposalResult);
if (ArrayUtil.contains(proposalResult, ProposalConstant.DETERMINE)) {
if (determineTypeOptions != null && determineTypeOptions.length > 0) {
cnd.and("info.determineTypeCode", "in", determineTypeOptions);
}
}
}
cnd.asc("info.resultCode");
proposalInfoService.sortAndSearch(cnd, page, search);
@@ -189,8 +204,17 @@ public class proposalExportCaseController {
}
try {
ViTool.excelResponse(response, "提案立案汇总表.xlsx");
ExportParams exportParams = new ExportParams();
String exportTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
String fileCondition = StrUtil.blankToDefault(exportFileCondition, "全部立案提案")
.replaceAll("[\\\\/:*?\"<>|\\r\\n]+", "-")
.replaceAll("\\s+", "");
if (fileCondition.length() > 80) {
fileCondition = fileCondition.substring(0, 80);
}
ViTool.excelResponse(response, "提案立案汇总表_" + fileCondition + "_"
+ exportTime.replaceAll("[-: ]", "") + ".xlsx");
ExportParams exportParams = new ExportParams("提案立案汇总表(查询条件:"
+ StrUtil.blankToDefault(exportCondition, "无") + ";导出时间:" + exportTime + ")", "立案汇总");
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, proposalList);
workbook.write(response.getOutputStream());
@@ -11,6 +11,7 @@ import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.modules.models.PageForm;
import io.v.nutz.proposal.constants.ProposalConstant;
import io.v.nutz.proposal.constants.ProposalState;
import io.v.nutz.proposal.models.ProposalSearch;
import io.v.nutz.proposal.services.ProposalInfoService;
import io.v.nutz.util.ShiroUtil;
@@ -31,8 +32,12 @@ import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author zhf
@@ -151,12 +156,15 @@ public class proposalExportSummaryController {
Sql sql = Sqls.create("""
SELECT
info.id,
info.proposalCode,
info.proposalName,
info.brief,
info.measures,
info.createTime,
su.username,
su.loginname,
creatorUnit.`name` creatorUnitName,
state.stateName,
state.stateColor,
type.typeCode,
@@ -170,16 +178,24 @@ public class proposalExportSummaryController {
masterPu.unitCode AS pruMasterCode,
masterPr.replyContent,
masterPr.handleOpinion,
GROUP_CONCAT(slavePu.unitName) AS pruSlave,
masterPr.replyTime,
masterPr.implementState,
masterPr.leaderCheckTime,
masterPr.leaderCheckOpinion,
CASE WHEN masterPr.isReply = 1 THEN '已答复' ELSE '未答复' END replyStatus,
GROUP_CONCAT(DISTINCT slavePu.unitName) AS pruSlave,
pf.feedbackCodeByUnderTake,
pfDict.`name` feedbackResult
pfDict.`name` feedbackResult,
pf.feedbackOpinion,
pf.feedbackTime
FROM
proposal_info info
LEFT JOIN proposal_reply masterPr on masterPr.proposalId = info.id AND masterPr.undertakeType = 1
LEFT JOIN proposal_underTake masterPu on masterPu.id = masterPr.replyUnitId
LEFT JOIN proposal_reply slavePr on slavePr.proposalId = info.id AND slavePr.undertakeType = 2
LEFT JOIN proposal_underTake slavePu on slavePu.id = slavePr.replyUnitId
LEFT JOIN `user` su ON su.id = info.createUser
LEFT JOIN sys_user su ON su.id = info.createUser
LEFT JOIN sys_unit creatorUnit ON creatorUnit.id = su.unitid
LEFT JOIN proposal_state state ON state.stateCode = info.stateCode
LEFT JOIN proposal_type type ON type.id = info.typeId
LEFT JOIN jdh_jdhxx jdh ON jdh.id = info.teacherMeetingId
@@ -207,20 +223,36 @@ public class proposalExportSummaryController {
List<String> proposalResult = mapCnd.getAsList("proposalResult", String.class);
cnd.andEX("info.resultCode", "in", proposalResult);
if (proposalResult != null && proposalResult.contains(ProposalConstant.DETERMINE)) {
List<String> determineTypeOptions = mapCnd.getAsList("proposalDetermineTypes", String.class);
cnd.andEX("info.determineTypeCode", "in", determineTypeOptions);
}
cnd.andEX("info.delegationId", "=", mapCnd.getString("delegationId"));
cnd.andEX("info.stateCode", "=", mapCnd.getString("stateCode"));
if (Boolean.TRUE.equals(mapCnd.getBoolean("caseOnly"))) {
cnd.and("info.stateCode", ">", ProposalState.CASE);
}
cnd.andEX("su.unionId", "=", mapCnd.getString("unionId"));
cnd.andEX("su.unitId", "=", mapCnd.getString("unitId"));
if (!ShiroUtil.hasAnyRoles(new String[]{"sysadmin", "A06", "tamange", "jdhdbt01"})) {
if (ShiroUtil.hasRole("H04")) {
cnd.and("su.unionid", " in ", vi.getMangeUnionStr());
}
}
cnd.asc("info.proposalCode");
cnd.groupBy("info.id");
sql.setCondition(cnd);
List<NutMap> list = proposalInfoService.listMap(sql);
fillExportDetails(list);
list.forEach(v -> {
v.put("brief", HtmlUtil.cleanHtmlTag(HtmlUtil.unescape(v.getString("brief"))));
v.put("measures", HtmlUtil.cleanHtmlTag(HtmlUtil.unescape(v.getString("measures"))));
v.put("replyContent", HtmlUtil.cleanHtmlTag(HtmlUtil.unescape(v.getString("replyContent", ""))));
v.put("leaderCheckOpinion", HtmlUtil.cleanHtmlTag(HtmlUtil.unescape(v.getString("leaderCheckOpinion", ""))));
v.put("feedbackOpinion", HtmlUtil.cleanHtmlTag(HtmlUtil.unescape(v.getString("feedbackOpinion", ""))));
});
List<ExcelExportEntity> excelExportEntities = new ArrayList<ExcelExportEntity>();
@@ -230,8 +262,22 @@ public class proposalExportSummaryController {
});
try {
ViTool.excelResponse(response, "提案表.xlsx");
ExportParams exportParams = new ExportParams();
String exportTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
String exportCondition = mapCnd.getString("exportCondition", "无").replaceAll("[\\r\\n]+", " ");
String exportFileCondition = mapCnd.getString("exportFileCondition", "")
.replaceAll("[\\\\/:*?\"<>|\\r\\n]+", "-")
.replaceAll("\\s+", "");
String exportTitle = mapCnd.getString("exportTitle", "提案汇总表")
.replaceAll("[\\\\/:*?\"<>|\\r\\n]+", "-");
if (exportFileCondition.length() > 80) {
exportFileCondition = exportFileCondition.substring(0, 80);
}
String fileName = exportTitle
+ (StrUtil.isBlank(exportFileCondition) ? "" : "_" + exportFileCondition)
+ "_" + exportTime.replaceAll("[-: ]", "") + ".xlsx";
ViTool.excelResponse(response, fileName);
ExportParams exportParams = new ExportParams(exportTitle + "(查询条件:" + exportCondition
+ ";导出时间:" + exportTime + ")", "提案汇总");
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, excelExportEntities, list);
workbook.write(response.getOutputStream());
@@ -242,4 +288,70 @@ public class proposalExportSummaryController {
}
/**
* 补充附议人和审核记录,避免数据库聚合长度限制导致导出内容不完整
*
* @param list 提案数据
*/
private void fillExportDetails(List<NutMap> list) {
if (list == null || list.isEmpty()) {
return;
}
List<String> proposalIds = new ArrayList<>();
list.forEach(v -> proposalIds.add(v.getString("id")));
Sql secondedSql = Sqls.create("""
SELECT
ps.proposalId,
su.username,
su.loginname
FROM proposal_seconded ps
LEFT JOIN sys_user su ON su.id = ps.seconderId
WHERE ps.proposalId IN (@proposalIds)
AND ps.isAgree = 1
ORDER BY ps.secondedTime
""").setParam("proposalIds", proposalIds);
Map<String, List<String>> secondedMap = new HashMap<>();
proposalInfoService.listMap(secondedSql).forEach(v -> {
String username = v.getString("username", "");
String loginname = v.getString("loginname", "");
String seconderName = username + (StrUtil.isBlank(loginname) ? "" : "(" + loginname + ")");
secondedMap.computeIfAbsent(v.getString("proposalId"), key -> new ArrayList<>()).add(seconderName);
});
Sql auditSql = Sqls.create("""
SELECT
pa.proposalId,
pa.username,
pa.flag,
pa.opinion,
pa.auditTime,
dict.`name` auditTypeName
FROM proposal_audit pa
LEFT JOIN sys_dict dict ON dict.`code` = pa.other
WHERE pa.proposalId IN (@proposalIds)
ORDER BY pa.auditTime
""").setParam("proposalIds", proposalIds);
Map<String, List<String>> auditMap = new HashMap<>();
proposalInfoService.listMap(auditSql).forEach(v -> {
Boolean flag = v.getBoolean("flag");
String auditResult = flag == null ? "" : (flag ? "通过" : "不通过");
String auditTypeName = v.getString("auditTypeName", "");
String auditTitle = StrUtil.isBlank(auditTypeName) ? auditResult
: auditTypeName + (StrUtil.isBlank(auditResult) ? "" : "/" + auditResult);
String opinion = HtmlUtil.cleanHtmlTag(HtmlUtil.unescape(v.getString("opinion", "")));
String auditRecord = v.getString("username", "")
+ (StrUtil.isBlank(auditTitle) ? "" : "【" + auditTitle + "】")
+ (StrUtil.isBlank(opinion) ? "" : ":" + opinion)
+ (StrUtil.isBlank(v.getString("auditTime")) ? "" : "(" + v.getString("auditTime") + ")");
auditMap.computeIfAbsent(v.getString("proposalId"), key -> new ArrayList<>()).add(auditRecord);
});
list.forEach(v -> {
v.put("seconderNames", String.join("、", secondedMap.getOrDefault(v.getString("id"), new ArrayList<>())));
v.put("auditRecords", String.join(";", auditMap.getOrDefault(v.getString("id"), new ArrayList<>())));
});
}
}
@@ -133,7 +133,8 @@ public class proposalExportWholeController {
@Ok("void")
@RequiresAuthentication
public void exportAllProposal(ProposalSearch search, String stateCode, String[] proposalTypes, String[] proposalResult,
String searchName2, String searchKeyword2, HttpServletResponse response) throws Exception {
String searchName2, String searchKeyword2, String exportFileCondition,
HttpServletResponse response) throws Exception {
CndPlus cnd = CndPlus.create();
Sql sql = Sqls.create("""
SELECT
@@ -171,7 +172,14 @@ public class proposalExportWholeController {
response.reset();
response.setContentType("application/x-msdownload;charset=utf-8");
String fileName = "提案汇总文件夹";
String exportTime = DateFormatUtils.format(new Date(), "yyyyMMddHHmmss", Locale.CHINA);
String fileCondition = StrUtil.blankToDefault(exportFileCondition, "全部提案")
.replaceAll("[\\\\/:*?\"<>|\\r\\n]+", "-")
.replaceAll("\\s+", "");
if (fileCondition.length() > 80) {
fileCondition = fileCondition.substring(0, 80);
}
String fileName = "提案汇总文件夹_" + fileCondition + "_" + exportTime;
// 创建临时文件夹
@@ -33,8 +33,17 @@ public class ProposalUnderTakeListener implements SysCommonDataChange {
case UPDATE -> {
Sys_unit sys_unit = dao.fetch(Sys_unit.class, bizId);
ProposalUndertake undertake = dao.fetch(ProposalUndertake.class, Cnd.where("unitCode", "=", sys_unit.getUnitcode()));
undertake.setUnitName(sys_unit.getName());
dao.updateIgnoreNull(undertake);
if (undertake == null) {
// 兼容历史单位缺少提案承办单位记录的情况
undertake = new ProposalUndertake();
undertake.setUnitCode(sys_unit.getUnitcode());
undertake.setUnitName(sys_unit.getName());
undertake.setIsEnable(Boolean.TRUE);
dao.insert(undertake);
} else {
undertake.setUnitName(sys_unit.getName());
dao.updateIgnoreNull(undertake);
}
}
case DELETE -> {
dao.clear(ProposalUndertake.class, Cnd.where("id", "=", bizId));
@@ -14,4 +14,9 @@ public interface SysConfigService extends BaseService<Sys_config> {
* @return
*/
List<Sys_config> getAllList();
/**
* 确保系统配置中存在学校待办开关;首次创建时默认关闭。
*/
void ensureTodoTaskConfig();
}
@@ -3,8 +3,10 @@ package io.v.nutz.sys.services.impl;
import io.v.nutz.base.service.impl.BaseServiceImpl;
import io.v.nutz.sys.models.Sys_config;
import io.v.nutz.sys.services.SysConfigService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.List;
@@ -21,4 +23,16 @@ public class SysConfigServiceImpl extends BaseServiceImpl<Sys_config> implements
public List<Sys_config> getAllList() {
return this.query(Cnd.where("delFlag", "=", false));
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void ensureTodoTaskConfig() {
if (fetch("TodoTaskEnabled") == null) {
Sys_config config = new Sys_config();
config.setConfigKey("TodoTaskEnabled");
config.setConfigValue("false");
config.setNote("启用学校统一待办推送、更新和删除;关闭时不调用学校待办网关");
insert(config);
}
}
}
@@ -5,6 +5,8 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import io.v.nutz.sys.models.Sys_config;
import io.v.nutz.sys.services.SysConfigService;
import io.v.nutz.web.commons.utils.ShiroUtil;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Dao;
@@ -26,6 +28,17 @@ import java.util.Map;
@Slf4j
public class TodoTaskInterfaceImpl implements TodoTaskInterface {
/**
* 只有系统配置明确开启时,才调用学校统一待办网关。
*/
private boolean isTodoTaskEnabled() {
Sys_config config = sysConfigService.fetch("TodoTaskEnabled");
return config != null && "true".equals(config.getConfigValue());
}
@Inject
private SysConfigService sysConfigService;
@Inject
private RedisService redisService;
@@ -52,6 +65,9 @@ public class TodoTaskInterfaceImpl implements TodoTaskInterface {
@Override
public void pushTask(TodoTask todoTask) {
if (!isTodoTaskEnabled()) {
return;
}
log.info("【推送待办入参】:" + Json.toJson(todoTask));
String accessToken = getAccessToken();
HttpRequest httpRequest = HttpUtil.createPost(TodoTaskConstants.PUSH_TASK_URL);
@@ -69,6 +85,9 @@ public class TodoTaskInterfaceImpl implements TodoTaskInterface {
@Override
public void updateTask(TodoTask todoTask) {
if (!isTodoTaskEnabled()) {
return;
}
log.info("【更新待办入参】:" + Json.toJson(todoTask));
String accessToken = getAccessToken();
HttpRequest httpRequest = HttpUtil.createPost(TodoTaskConstants.UPDATE_TASK_URL);
@@ -86,6 +105,9 @@ public class TodoTaskInterfaceImpl implements TodoTaskInterface {
@Override
public void deleteTask(TodoTask todoTask) {
if (!isTodoTaskEnabled()) {
return;
}
log.info("【删除待办入参】:" + Json.toJson(todoTask));
String accessToken = getAccessToken();
HttpRequest httpRequest = HttpUtil.createPost(TodoTaskConstants.DELETE_TASK_URL);
@@ -277,7 +277,7 @@ public class MTrainSignUpActivityController {
if(signFullByUnionId) {
return Result.error("该活动您所在的分工会名额已报满");
}
if(course.getReserveMode() == 2 && currentFamilyNumber == null) {
if(Integer.valueOf(2).equals(course.getReserveMode()) && currentFamilyNumber == null) {
//课程已报人数
List<TrainSignUpUser> signUpUsers = dao.query(TrainSignUpUser.class, Cnd.where("courseId", "=", courseId));
int hasRegisterNum = signUpUsers.size() + 1;//+1是算自己
@@ -336,7 +336,7 @@ public class TrainSignUpActivityServiceImpl extends ViServiceImpl<TrainSignUpAct
TrainSignUpActivity activity = dao().fetch(TrainSignUpActivity.class, Cnd.where("id", "=", course.getActivityId()));
//如果这个课程的预留名额方式为报名人数不变
if (course.getReserveMode() == 2) {
if (Integer.valueOf(2).equals(course.getReserveMode())) {
//如果当前报名+已报小于这个课程限制人数
//课程已报人数
List<TrainSignUpUser> signUpUsers = dao().query(TrainSignUpUser.class, Cnd.where("courseId", "=", trainSignUpUser.getCourseId()));
@@ -487,7 +487,7 @@ public class TrainSignUpActivityServiceImpl extends ViServiceImpl<TrainSignUpAct
hasFamilyNum = num.get() + (currentFamilyNumber != null ? currentFamilyNumber : 0);//currentFamilyNumber是当前报名时填的家属人数
}
//如果预留名额模式是报名人数减少
if (course.getReserveMode() == 1) {
if (course.getReserveMode() == null || course.getReserveMode() == 1) {
//如果已报人数+预留人数+当前报名(自己)+家属 > 总人数
return (hasRegisterNum + course.getCourseReservedNumber() + hasFamilyNum) > coursePeopleNumber;
} else {//如果预留名额模式是报名人数不变
@@ -34,7 +34,7 @@ public class SysConfController {
@Ok("beetl:/platform/sys/conf/index.html")
@RequiresPermissions("sys.manager.conf")
public void index() {
sysConfigService.ensureTodoTaskConfig();
}
@At
@@ -50,6 +50,7 @@ import java.util.stream.Collectors;
@Ok("json")
public class SysUnionMangeCon {
private static final Log log = Logs.get();
private static final String THIRD_UNIT_MANAGER_UNION_CODE = "28";
@Inject
private SysUnionService sysUnionService;
@@ -347,12 +348,120 @@ public class SysUnionMangeCon {
@At
public Object getUnitsByUnion(String unionid) {
try {
return Result.success().addData(sysUnitService.query(Cnd.where("unionid", "=", unionid).and("unitlevel", "=", 2)));
Sql sql = Sqls.create("""
SELECT un.*,
GROUP_CONCAT(DISTINCT CONCAT(u.username, '(', u.loginname, ')')) thirdManagerNames
FROM sys_unit un
LEFT JOIN sys_user_role sur ON sur.unitid = un.id AND sur.roleId = @roleId
LEFT JOIN sys_user u ON u.id = sur.userId
WHERE un.unionid = @unionId AND un.unitlevel = 2
GROUP BY un.id
ORDER BY un.location
""");
sql.setParam("roleId", Roles.THIRD_UNIT_MANGER);
sql.setParam("unionId", unionid);
return Result.success().addData(sysUnitService.list(sql));
} catch (Exception e) {
return Result.error();
}
}
/**
* 查询机关党委工会成员单位的可选人员和已设置管理员
*/
@At
public Object findThirdUnitManagerUsers(String unionId, String unitId) {
try {
checkThirdUnitManagerScope(unionId, unitId);
Sql userSql = Sqls.create("""
SELECT id, CONCAT(username, '(', loginname, ')') name
FROM `user`
WHERE unionid = @unionId
AND unitid = @unitId
ORDER BY username
""");
userSql.setParam("unionId", unionId);
userSql.setParam("unitId", unitId);
Sql selectedSql = Sqls.create("""
SELECT userId
FROM sys_user_role
WHERE roleId = @roleId AND unitid = @unitId
""");
selectedSql.setParam("roleId", Roles.THIRD_UNIT_MANGER);
selectedSql.setParam("unitId", unitId);
List<Record> selectedUsers = sysUnionService.list(selectedSql);
List<String> selectedUserIds = selectedUsers.stream()
.map(v -> v.getString("userId")).collect(Collectors.toList());
return Result.success().addData(Record.create()
.set("allUsers", sysUnionService.list(userSql))
.set("selectedUserIds", selectedUserIds));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
/**
* 设置机关党委工会成员单位管理员
*/
@At
@Aop(TransAop.READ_COMMITTED)
public Object doSetThirdUnitManagerUsers(String unionId, String unitId, String userIds) {
try {
checkThirdUnitManagerScope(unionId, unitId);
List<String> ids = Strings.isBlank(userIds)
? new java.util.ArrayList<>() : Json.fromJsonAsList(String.class, userIds);
for (String userId : ids) {
Sql userCheckSql = Sqls.create("""
SELECT id
FROM `user`
WHERE id = @userId
AND unionid = @unionId
AND unitid = @unitId
LIMIT 1
""");
userCheckSql.setParam("userId", userId);
userCheckSql.setParam("unionId", unionId);
userCheckSql.setParam("unitId", unitId);
if (sysUnionService.list(userCheckSql).isEmpty()) {
return Result.error("所选人员不属于当前成员单位");
}
}
List<Sys_user_role> userRoles = new java.util.ArrayList<>();
for (String userId : ids) {
Sys_user_role userRole = new Sys_user_role();
userRole.setRoleId(Roles.THIRD_UNIT_MANGER);
userRole.setUserId(userId);
userRole.setUnitid(unitId);
userRoles.add(userRole);
}
sysUserRoleService.clear(Cnd.where("roleId", "=", Roles.THIRD_UNIT_MANGER)
.and("unitid", "=", unitId));
if (!userRoles.isEmpty()) {
sysUserRoleService.dao().insert(userRoles);
}
sysUserService.clearCache();
sysRoleService.clearCache();
return Result.success();
} catch (Exception e) {
log.error(e);
return Result.error(e.getMessage());
}
}
private void checkThirdUnitManagerScope(String unionId, String unitId) {
if (!checkRole()) {
throw new SecurityException("仅校工会管理员可设置三级管理员");
}
Sys_union union = sysUnionService.fetch(unionId);
if (union == null || !THIRD_UNIT_MANAGER_UNION_CODE.equals(Strings.trim(union.getUnioncode()))) {
throw new IllegalArgumentException("仅机关党委工会的成员单位可设置三级管理员");
}
Sys_unit unit = sysUnitService.fetch(unitId);
if (unit == null || !unionId.equals(unit.getUnionid()) || unit.getUnitlevel() != 2) {
throw new IllegalArgumentException("成员单位不属于机关党委工会");
}
}
@At // 会员档案
public Object hyPageData(String unitid, String unionid, String searchName, String searchKeyword, int pageNumber, int pageSize, String pageOrderName, String pageOrderBy) {
@@ -12,13 +12,11 @@ import io.v.nutz.sys.models.Sys_menu;
import io.v.nutz.sys.models.Sys_user;
import io.v.nutz.util.ShiroUtil;
import io.v.nutz.zgfw.service.CondolenceService;
import org.nutz.dao.Cnd;
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.Strings;
import org.nutz.lang.util.NutMap;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
@@ -53,71 +51,24 @@ public class CondolenceAgentController {
List<NutMap> result = new ArrayList<>();
result.add(new NutMap().setv("title", title).setv("url", UCR).setv("iconClass", vi.getIconByPath(UCR)).setv("label", "待部门工会审核").setv("number", getUcrCount()));
result.add(new NutMap().setv("title", title).setv("url", FR).setv("iconClass", vi.getIconByPath(FR)).setv("label", "待二级党组织审核").setv("number", getFrCount()));
result.add(new NutMap().setv("title", title).setv("url", VCR).setv("iconClass", vi.getIconByPath(VCR)).setv("label", "待校工会审核").setv("number", getVcrCount()));
result.add(new NutMap().setv("title", title).setv("url", ECR).setv("iconClass", vi.getIconByPath(ECR)).setv("label", "待工会常务副主席审核").setv("number", getEcrCount()));
result.add(new NutMap().setv("title", title).setv("url", UCR).setv("iconClass", vi.getIconByPath(UCR)).setv("label", "待部门工会审核").setv("number", condolenceService.countCurrentYearAgenda(2000)));
result.add(new NutMap().setv("title", title).setv("url", FR).setv("iconClass", vi.getIconByPath(FR)).setv("label", "待二级党组织审核").setv("number", condolenceService.countCurrentYearAgenda(2012)));
result.add(new NutMap().setv("title", title).setv("url", VCR).setv("iconClass", vi.getIconByPath(VCR)).setv("label", "待校工会审核").setv("number", condolenceService.countCurrentYearAgenda(2020)));
result.add(new NutMap().setv("title", title).setv("url", ECR).setv("iconClass", vi.getIconByPath(ECR)).setv("label", "待工会常务副主席审核").setv("number", condolenceService.countCurrentYearAgenda(2050)));
Sys_user user = (Sys_user) ShiroUtil.getPrincipal();
List<Sys_menu> menus = Optional.ofNullable(user).orElse(new Sys_user()).getMenus();
return result.stream().filter(v -> Strings.isNotBlank(v.getString("url")) && menus.stream().anyMatch(x -> Strings.isNotBlank(x.getHref()) && v.getString("url").equals(x.getHref()))).distinct().collect(Collectors.toList());
}
/**
* 待工会主席审核
*
* @return
* 无需传参,身份取自登录会话,返回今年本人办理的慰问记录列表。
* 每项包含 moduleName(业务名称)、auditState(办理节点)、auditTime(审核日期)、mobileUrl(详情链接)。
*/
private Integer getUcrCount() {
Cnd cnd = Cnd.NEW();
Sql sql = Sqls.create("""
SELECT COUNT(1) FROM condolence con LEFT JOIN `user` us ON con.be_user=us.id $condition
""");
cnd.and("con.state_id", "=", 2000);
if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) {
cnd.and("us.unionid", "=", vi.getUnionId());
}
sql.setCondition(cnd);
return condolenceService.count(sql);
@At
@ViReturn
@RequiresAuthentication
public Object getCompletes() {
return condolenceService.getCurrentYearCompletes();
}
/**
* 待工会会计审核
* 暂时没用
*
* @return
*/
private Integer getFrCount() {
Cnd cnd = Cnd.NEW();
Sql sql = Sqls.create("""
SELECT COUNT(1) FROM condolence con LEFT JOIN `user` us ON con.be_user=us.id $condition
""");
cnd.and("con.state_id", "=", 2012);
if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) {
cnd.and("us.unionid", "=", vi.getUnionId());
}
sql.setCondition(cnd);
return condolenceService.count(sql);
}
/**
* 待校工会主席审核
*
* @return
*/
private Integer getVcrCount() {
return condolenceService.count(Cnd.where("state_id", "=", 2020));
}
/**
* 待工会常务副主席审核
* 暂时没用
*
* @return
*/
private Integer getEcrCount() {
return condolenceService.count(Cnd.where("state_id", "=", 2050));
}
}
@@ -27,6 +27,8 @@ import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
@@ -98,7 +100,12 @@ public class CondolenceApplyCon {
@At
@ViReturn
@Aop(TransAop.READ_COMMITTED)
public Object doAdd(@Param("data") Condolence condolence, String sign) {
if (condolenceService.hasHospitalCondolenceInCurrentYear(condolence)) {
return Result.error().addMsg("住院慰问每年只能申请一次,请于下一年度再申请");
}
List<Sys_file> files = condolence.getFiles();
if (Lang.isEmpty(files)) {
condolence.setFiles_num("0");
@@ -5,6 +5,7 @@ import io.v.nutz.service.ViService;
import io.v.nutz.zgfw.model.condolence.Condolence;
import org.nutz.dao.entity.Record;
import org.nutz.lang.util.NutMap;
import java.util.List;
/**
@@ -19,6 +20,28 @@ public interface CondolenceService extends ViService<Condolence> {
*/
NutMap findOne(String id);
/**
* 无参数;按当前会话工号及本自然年审核日期查询本人慰问已办。
* @return 列表项包含 id、reviewId、moduleName、auditState、auditTime、mobileUrl;按审核日期倒序
*/
List<NutMap> getCurrentYearCompletes();
/**
* 统计当前登录用户可见范围内、申请时间属于当前自然年的慰问待办数量。
*
* @param stateId 审核状态:2000 部门工会、2012 二级党组织、2020 校工会、2050 历史常务副主席待办
* @return 待办数量(Integer);不支持的状态返回 0,权限菜单过滤仍由待办接口保留
*/
Integer countCurrentYearAgenda(int stateId);
/**
* 判断被慰问人在当前自然年是否已有住院慰问申请。
*
* @param application 本次申请,包含慰问类型、被慰问人和编辑时的申请 ID
* @return 已有其他同年住院慰问申请时返回 true,否则返回 false
*/
boolean hasHospitalCondolenceInCurrentYear(Condolence application);
/**
* 添加
*
@@ -2,9 +2,13 @@ package io.v.nutz.zgfw.service.impl;
import cn.wizzer.framework.base.service.BaseService;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.util.ShiroUtil;
import io.v.nutz.modules.services.ReviewService;
import io.v.nutz.service.impl.ViServiceImpl;
import io.v.nutz.zgfw.model.condolence.Condolence;
import io.v.nutz.zgfw.model.condolence.CondolenceType;
import io.v.nutz.zgfw.service.CondolenceService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
@@ -17,6 +21,8 @@ import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import java.util.List;
import java.util.Collections;
/**
* @author 1V
@@ -28,9 +34,102 @@ public class CondolenceServiceImpl extends ViServiceImpl<Condolence> implements
private ReviewService reviewService;
@Inject
private BaseService baseService;
private final Dao condolenceDao;
public CondolenceServiceImpl(Dao dao) {
super(dao);
this.condolenceDao = dao;
}
/**
* 按审核状态统计本自然年申请,保留原待办对部门工会、二级党组织节点的工会范围限制。
*
* @param stateId 2000、2012、2020 或 2050;其他值不属于本接口的待办节点
* @return 当前节点的待办条数,不包含往年或下一年度申请
*/
/**
* 按审核记录的办理人工号及办理年份筛选,不将申请当前状态误作个人审核结果。
* @return 本人今年的已办列表;未获得登录工号时返回空列表,禁止扩大到全员记录
*/
@Override
public List<NutMap> getCurrentYearCompletes() {
Object principalLoginname = ShiroUtil.getPrincipalProperty("loginname");
String loginname = principalLoginname == null ? "" : principalLoginname.toString();
if (Strings.isBlank(loginname)) {
return Collections.emptyList();
}
int year = DateUtil.getYear();
// 只读取申请当前关联的审核记录;再次审核覆盖的旧关联无法在此恢复。
Sql sql = Sqls.create("""
SELECT con.id, r.id reviewId,
CONCAT('【职工慰问】', COALESCE(con.be_user_username, '')) moduleName,
nodes.nodeName auditState, r.time auditTime,
CONCAT('/mobile/condolence/apply/info?id=', con.id) mobileUrl
FROM condolence con
JOIN (
SELECT 1 nodeId, '部门工会审核' nodeName
UNION ALL SELECT 2, '二级党组织审核'
UNION ALL SELECT 3, '校工会审核'
UNION ALL SELECT 4, '工会常务副主席审核'
) nodes ON 1=1
JOIN review r ON r.id = CASE nodes.nodeId
WHEN 1 THEN con.union_chairman_review
WHEN 2 THEN con.finance_review
WHEN 3 THEN con.vice_chairman_review
WHEN 4 THEN con.executive_chairman_review END
WHERE r.loginname = @loginname AND r.time >= @startTime AND r.time < @endTime
ORDER BY r.time DESC, con.id, nodes.nodeId
""");
sql.setParam("loginname", loginname);
sql.setParam("startTime", year + "-01-01");
sql.setParam("endTime", (year + 1) + "-01-01");
return listMap(sql);
}
@Override
public Integer countCurrentYearAgenda(int stateId) {
if (stateId != 2000 && stateId != 2012 && stateId != 2020 && stateId != 2050) {
return 0;
}
// 年份只读取一次;左闭右开范围包含年初当天及年末全部时间,跨年后自动切换。
int year = DateUtil.getYear();
if (stateId == 2000 || stateId == 2012) {
Cnd cnd = Cnd.where("con.state_id", "=", stateId)
.and("con.apply_time", ">=", year + "-01-01")
.and("con.apply_time", "<", (year + 1) + "-01-01");
// 沿用原待办的角色判断:系统管理员和校工会管理员不限制所属工会。
if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) {
cnd.and("us.unionid", "=", Vi.getUnionId());
}
Sql sql = Sqls.create("""
SELECT COUNT(1) FROM condolence con LEFT JOIN `user` us ON con.be_user=us.id $condition
""");
sql.setCondition(cnd);
return count(sql);
}
// 校级节点保留原有全校统计范围,仅追加申请年份条件。
return count(Cnd.where("state_id", "=", stateId)
.and("apply_time", ">=", year + "-01-01")
.and("apply_time", "<", (year + 1) + "-01-01"));
}
@Override
public boolean hasHospitalCondolenceInCurrentYear(Condolence application) {
CondolenceType type = condolenceDao.fetch(CondolenceType.class, application.getType());
if (type == null || !"C".equals(type.getCode())) {
return false;
}
// 按被慰问人和申请年份限制次数;编辑时排除当前申请,已退回的其他申请仍计入次数。
String year = String.valueOf(DateUtil.getYear());
Cnd cnd = Cnd.where("be_user", "=", application.getBe_user())
.and("type", "=", application.getType())
.and("apply_time", ">=", year + "-01-01")
.and("apply_time", "<", (DateUtil.getYear() + 1) + "-01-01");
if (Strings.isNotBlank(application.getId())) {
cnd.and("id", "!=", application.getId());
}
return count(cnd) > 0;
}
@Override
@@ -87,6 +186,14 @@ public class CondolenceServiceImpl extends ViServiceImpl<Condolence> implements
if (Strings.isNotBlank(record.getString("union_chairman_review"))) {
record.put("ucr", reviewService.fetch(record.getString("union_chairman_review")));
}
// 二级党组织审核沿用 finance_review 字段,详情以 fr 返回审核人、时间、意见和签字。
if (Strings.isNotBlank(record.getString("finance_review"))) {
record.put("fr", reviewService.fetch(record.getString("finance_review")));
}
// 历史常务副主席审核记录仅用于展示,不改变当前审批流转。
if (Strings.isNotBlank(record.getString("executive_chairman_review"))) {
record.put("ecr", reviewService.fetch(record.getString("executive_chairman_review")));
}
//校工会
if (Strings.isNotBlank(record.getString("vice_chairman_review"))) {
record.put("vcr", reviewService.fetch(record.getString("vice_chairman_review")));
@@ -0,0 +1,95 @@
/*
体育活动联合工会管理员角色初始化脚本
说明:本脚本创建新角色、复制H04在指定体育活动菜单下的权限,并为现有联合工会的历史管理员补充新角色;不删除任何H04用户关系。
*/
SET @sports_activity_union_admin_role_id = 'b72001e1783448edbba585b441955676';
SET @h04_role_id = '5d342e614e9a48288d50154bcdcc75d3';
INSERT INTO sys_role
(id, name, code, aliasName, disabled, note, createdBy, createdAt, updatedBy, updatedAt, delFlag)
SELECT
@sports_activity_union_admin_role_id,
'体育活动联合工会管理员',
'SportsActivityUnionAdmin',
'体育活动联合工会管理员',
0,
'仅用于体育活动联合工会报名和管理,不属于组织机构H04角色',
'',
0,
'',
0,
0
WHERE NOT EXISTS (
SELECT 1
FROM sys_role
WHERE id = @sports_activity_union_admin_role_id
OR code = 'SportsActivityUnionAdmin'
);
INSERT INTO sys_role_menu (roleId, menuId)
SELECT DISTINCT
@sports_activity_union_admin_role_id,
parent_menu.id
FROM sys_menu parent_menu
INNER JOIN sys_role_menu h04_parent
ON h04_parent.menuId = parent_menu.id
AND h04_parent.roleId = @h04_role_id
WHERE EXISTS (
SELECT 1
FROM sys_menu target_menu
INNER JOIN sys_role_menu h04_target
ON h04_target.menuId = target_menu.id
AND h04_target.roleId = @h04_role_id
WHERE target_menu.path LIKE CONCAT(parent_menu.path, '%')
AND (
target_menu.permission = 'activity.apply'
OR target_menu.permission LIKE 'activity.apply.%'
OR target_menu.permission = 'activity.reading'
OR target_menu.permission LIKE 'activity.reading.%'
OR target_menu.permission = 'activity.union.apply'
OR target_menu.permission LIKE 'activity.union.apply.%'
OR target_menu.permission = 'activity.union.info'
OR target_menu.permission LIKE 'activity.union.info.%'
OR target_menu.permission = 'activity.basic.scope'
OR target_menu.permission LIKE 'activity.basic.scope.%'
OR target_menu.permission = 'activity.basic.user'
OR target_menu.permission LIKE 'activity.basic.user.%'
)
)
AND NOT EXISTS (
SELECT 1
FROM sys_role_menu current_role_menu
WHERE current_role_menu.roleId = @sports_activity_union_admin_role_id
AND current_role_menu.menuId = parent_menu.id
);
/*
为当前仍有组成单位的联合工会历史H04管理员补充体育活动专用角色。
只新增缺失关系,不修改或删除原H04关系;重复执行不会重复插入。
*/
INSERT INTO sys_user_role (roleId, userId, unionid)
SELECT DISTINCT
@sports_activity_union_admin_role_id,
historical_admin.userId,
historical_admin.unionid
FROM sys_user_role historical_admin
INNER JOIN activity_basic_union activity_union
ON activity_union.id = historical_admin.unionid
WHERE historical_admin.roleId = @h04_role_id
AND historical_admin.userId IS NOT NULL
AND historical_admin.userId <> ''
AND historical_admin.unionid IS NOT NULL
AND historical_admin.unionid <> ''
AND EXISTS (
SELECT 1
FROM activity_basic_unit activity_unit
WHERE activity_unit.unionid = historical_admin.unionid
)
AND NOT EXISTS (
SELECT 1
FROM sys_user_role current_admin
WHERE current_admin.roleId = @sports_activity_union_admin_role_id
AND current_admin.userId = historical_admin.userId
AND current_admin.unionid = historical_admin.unionid
);
@@ -0,0 +1,77 @@
/*
分级核对与审核初始化脚本
说明:脚本可重复执行,不自动给任何用户分配三级单位管理员角色。
*/
SET @third_unit_manager_role_id = 'e8d957d1ab9f4e56a60b5a53929d18bb';
SET @school_union_admin_role_id = '9b01918f873645048f8a85a4fc06d135';
SET @union_manager_role_id = '5d342e614e9a48288d50154bcdcc75d3';
SET @sysadmin_role_id = (SELECT id FROM sys_role WHERE code = 'sysadmin' LIMIT 1);
SET @level_check_root_menu_id = 'a63f7fd56f594b29b2b5616d0d7f0101';
SET @level_check_school_menu_id = 'a63f7fd56f594b29b2b5616d0d7f0102';
SET @level_check_unit_menu_id = 'a63f7fd56f594b29b2b5616d0d7f0103';
SET @third_admin_union_id = (SELECT id FROM sys_union WHERE unioncode = '28' ORDER BY id LIMIT 1);
INSERT INTO sys_role
(id, name, code, aliasName, disabled, note, createdBy, createdAt, updatedBy, updatedAt, delFlag)
SELECT @third_unit_manager_role_id, '三级单位管理员', 'ThirdUnitCheckAdmin', '三级单位管理员',
0, '机关党委工会成员单位的数据核对管理员', '', 0, '', 0, 0
WHERE NOT EXISTS (
SELECT 1 FROM sys_role
WHERE id = @third_unit_manager_role_id OR code = 'ThirdUnitCheckAdmin'
);
INSERT INTO sys_menu
(id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled,
permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag)
SELECT @level_check_root_menu_id, '', '0098', '分级核对', 'LevelCheck', 'menu', '', '',
'ti-check-box', 1, 0, 'levelCheck', NULL, 980, 1, '', 0, '', 0, 0
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE permission = 'levelCheck');
INSERT INTO sys_menu
(id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled,
permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag)
SELECT @level_check_school_menu_id, @level_check_root_menu_id, '00980001', '分级核对管理',
'LevelCheckSchool', 'menu', '/platform/levelCheck/school', 'data-pjax', '', 1, 0,
'levelCheck.school', NULL, 981, 0, '', 0, '', 0, 0
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE permission = 'levelCheck.school');
INSERT INTO sys_menu
(id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled,
permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag)
SELECT @level_check_unit_menu_id, @level_check_root_menu_id, '00980002', '单位数据核对',
'LevelCheckUnit', 'menu', '/platform/levelCheck/unit', 'data-pjax', '', 1, 0,
'levelCheck.unit', NULL, 982, 0, '', 0, '', 0, 0
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE permission = 'levelCheck.unit');
INSERT INTO sys_role_menu (roleId, menuId)
SELECT role_menu.roleId, role_menu.menuId
FROM (
SELECT @sysadmin_role_id roleId, @level_check_root_menu_id menuId
UNION ALL SELECT @sysadmin_role_id, @level_check_school_menu_id
UNION ALL SELECT @sysadmin_role_id, @level_check_unit_menu_id
UNION ALL SELECT @school_union_admin_role_id, @level_check_root_menu_id
UNION ALL SELECT @school_union_admin_role_id, @level_check_school_menu_id
UNION ALL SELECT @union_manager_role_id, @level_check_root_menu_id
UNION ALL SELECT @union_manager_role_id, @level_check_unit_menu_id
UNION ALL SELECT @third_unit_manager_role_id, @level_check_root_menu_id
UNION ALL SELECT @third_unit_manager_role_id, @level_check_unit_menu_id
) role_menu
WHERE NOT EXISTS (
SELECT 1 FROM sys_role_menu current_role_menu
WHERE current_role_menu.roleId = role_menu.roleId
AND current_role_menu.menuId = role_menu.menuId
);
INSERT INTO sys_config
(configKey, configValue, note, createdBy, createdAt, updatedBy, updatedAt, delFlag)
SELECT 'LevelCheckThirdAdminUnionId', IFNULL(@third_admin_union_id, ''),
'允许设置三级管理员的特殊工会ID', '', 0, '', 0, 0
WHERE NOT EXISTS (
SELECT 1 FROM sys_config WHERE configKey = 'LevelCheckThirdAdminUnionId'
);
UPDATE sys_config
SET configValue = IFNULL(@third_admin_union_id, configValue),
note = '允许设置三级管理员的特殊工会ID'
WHERE configKey = 'LevelCheckThirdAdminUnionId';
@@ -0,0 +1,4 @@
update sys_app_menu set moduleId = '282ea934ff624b7b9e279eab85a8ce39'
where moduleId = '956eeb9432424dbab7f1cd46950f228d';
delete from sys_app_module where moduleId = '956eeb9432424dbab7f1cd46950f228d';
@@ -38,10 +38,14 @@ window.activity = {
2: '部门工会报名',
3: '小程序报名'
},
getStatus(close, applyStartTime, applyEndTime, startTime, endTime) {
getStatus(close, applyStartTime, applyEndTime, startTime, endTime, isSave) {
if (close) {
return `<span class="text-danger">活动已关闭</span>`
}
// 管理列表传入 isSave;已保存但未提交的活动尚未开放报名。
if (isSave === true || isSave === 1 || isSave === '1') {
return `<span class="text-info">未提交</span>`
}
const now = moment().valueOf()
const dateNow = moment(moment(moment().format('YYYY-MM-DD')).valueOf()).valueOf()
@@ -190,6 +190,133 @@ window.proposal = {
async getProposalNextNode(stateCode) {
const {data} = await $.get("/platform/proposal/common/getProposalNextNode", {stateCode})
return data
},
/**
* 生成提案导出的查询条件说明
*/
buildExportCondition(app) {
const page = app.pageForm
const optionLabel = (options, value, valueKey, labelKey) => {
const option = (options || []).find(item => item[valueKey] === value)
return option ? option[labelKey] : value
}
const conditions = []
const fileConditions = []
if (page.meetingId) {
const meetingName = optionLabel(app.meetingOptions, page.meetingId, 'id', 'jdhallname')
conditions.push('届次:' + meetingName)
fileConditions.push(meetingName)
} else {
conditions.push('届次:全部届次')
fileConditions.push('全部届次')
}
if (page.stateCode) {
const stateName = optionLabel(app.stateOptions, page.stateCode, 'stateCode', 'stateName')
conditions.push('提案状态:' + stateName)
fileConditions.push(stateName)
}
if (page.proposalTypes.length) {
const typeNames = page.proposalTypes.map(value =>
optionLabel(app.typeOptions, value, 'id', 'typeName')).join('、')
conditions.push('提案类型:' + typeNames)
fileConditions.push(typeNames)
}
if (page.proposalResult.length) {
const resultNames = page.proposalResult.map(value =>
optionLabel(app.resultOptions, value, 'code', 'name')).join('、')
conditions.push('立案结果:' + resultNames)
fileConditions.push(resultNames)
}
if (page.proposalResult.includes('determine') && page.proposalDetermineTypes.length) {
conditions.push('是否重点:' + page.proposalDetermineTypes.map(value =>
optionLabel(app.determineTypeOptions, value, 'code', 'name')).join('、'))
}
if (page.delegationId) {
conditions.push('代表团:' + optionLabel(app.delegationOptions, page.delegationId, 'id', 'dbtname'))
}
if (page.unionId) {
conditions.push('所属工会:' + optionLabel(app.unionOptions, page.unionId, 'id', 'unionname'))
}
if (page.unitId) {
conditions.push('所属单位:' + optionLabel(app.unitOptions, page.unitId, 'id', 'name'))
}
if (page.undertakeId) {
const undertakeName = optionLabel(app.undertakeOptions, page.undertakeId, 'id', 'unitName')
conditions.push('承办单位:' + undertakeName)
fileConditions.push(undertakeName)
}
if (page.searchKeyword) {
conditions.push((page.searchName === 'proposalCode' ? '提案编号:' : '提案名称:') + page.searchKeyword)
fileConditions.push(page.searchKeyword)
}
if (page.searchKeyword2) {
conditions.push('提案人:' + page.searchKeyword2)
fileConditions.push(page.searchKeyword2)
}
return {
exportCondition: conditions.join(';'),
exportFileCondition: fileConditions.join('_')
}
},
/**
* 按当前页面查询条件导出完整提案数据
*/
exportCompleteData(app, exportOptions = {}) {
const page = app.pageForm
const {exportCondition, exportFileCondition} = this.buildExportCondition(app)
const totalCount = page.totalCount || 0
app.$confirm('将导出当前筛选条件下的全部 ' + totalCount + ' 条提案,不受当前分页影响。查询条件:'
+ exportCondition, '导出确认', {
confirmButtonText: '确认导出',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const exportForm = clone(page)
exportForm.exportCondition = exportCondition
exportForm.exportFileCondition = exportFileCondition
exportForm.exportTitle = exportOptions.title || '提案完整数据'
exportForm.caseOnly = exportOptions.caseOnly === true
const form = $('<form method="post" target="_blank"></form>')
form.attr('action', '/platform/proposal/export/summary/exportByCnd')
form.append($('<input type="hidden" name="cnd">').val(JSON.stringify(exportForm)))
form.append($('<input type="hidden" name="tableColumns">').val(JSON.stringify([
{prop: 'proposalCode', label: '提案编号'},
{prop: 'username', label: '提案人'},
{prop: 'loginname', label: '提案人工号'},
{prop: 'seconderNames', label: '附议人'},
{prop: 'creatorUnitName', label: '所属单位'},
{prop: 'proposalName', label: '提案标题'},
{prop: 'brief', label: '案由'},
{prop: 'measures', label: '建议措施'},
{prop: 'createTime', label: '提案时间'},
{prop: 'meetingName', label: '届次'},
{prop: 'delegationName', label: '代表团'},
{prop: 'typeName', label: '提案类型'},
{prop: 'mannerName', label: '提案方式'},
{prop: 'resultName', label: '立案结果'},
{prop: 'stateName', label: '办理进度'},
{prop: 'pruMaster', label: '主办单位'},
{prop: 'pruSlave', label: '协办单位'},
{prop: 'replyStatus', label: '答复状态'},
{prop: 'replyTime', label: '答复时间'},
{prop: 'replyContent', label: '答复内容'},
{prop: 'handleOpinion', label: '处理意见'},
{prop: 'implementState', label: '落实情况'},
{prop: 'leaderCheckTime', label: '分管领导审批时间'},
{prop: 'leaderCheckOpinion', label: '分管领导审批意见'},
{prop: 'feedbackCodeByUnderTake', label: '对承办单位办理态度评价'},
{prop: 'feedbackResult', label: '对办理结果评价'},
{prop: 'feedbackOpinion', label: '反馈意见'},
{prop: 'feedbackTime', label: '反馈时间'},
{prop: 'auditRecords', label: '审核记录及时间'}
])))
$('body').append(form)
form.submit()
form.remove()
}).catch(() => {})
}
}
@@ -11,6 +11,21 @@
label-suffix=":">
<vi-title title="填写活动信息"></vi-title>
<!-- 沿用入口只用于创建活动,独占首行,宽度与下方单个表单字段一致。 -->
<el-row gutter="40" v-if="!form.id">
<el-col :span="8">
<el-form-item label="沿用之前活动">
<el-select v-model="reuseActivityId" clearable filterable
:loading="reuseLoading" :disabled="formLoading"
placeholder="请选择要沿用的活动" @change="reuseActivityChange"
style="width: 100%">
<el-option v-for="item in reuseActivityOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row gutter="40">
<el-col :span="8">
<el-form-item prop="activityLevel" label="活动级别">
@@ -620,6 +635,12 @@ module.exports = {
planList: [],
events: [],
projectType: "",
reuseActivityId: null,
reuseActivityOptions: [],
reuseLoading: false,
reuseReady: false,
formLoading: false,
reuseRequestId: 0,
formRules: {
name: [{required: true, message: '必填', trigger: ['blur', 'change']}],
address: [{required: true, message: '必填', trigger: ['blur', 'change']}],
@@ -627,9 +648,7 @@ module.exports = {
eventsIds: [{required: true, message: '必填', trigger: ['blur', 'change']}],
applyTime: [{required: true, message: '必填', trigger: ['blur', 'change']}],
unitSponsor: [{required: true, message: '必填', trigger: ['blur', 'change']}],
undertakeUnit: [{required: true, message: '必填', trigger: ['blur', 'change']}],
// unitJointly: [{required: true, message: '必填', trigger: ['blur', 'change']}],
time: [{required: true, message: '必填', trigger: ['blur', 'change']}],
activityGroupId: [{required: true, message: '必填', trigger: ['blur', 'change']}],
gameStyle: [{required: true, message: '必填', trigger: ['blur', 'change']}],
applyWay: [{required: true, message: '必填', trigger: ['blur', 'change']}],
@@ -652,6 +671,205 @@ module.exports = {
},
methods: {
// 下拉接口无参数;data 为当前管理范围内的 {id, name} 数组。
getReuseActivityOptions() {
return $.post('/platform/activity/school/info/mange/getReuseActivityOptions').then((resp) => {
if (resp.code === 0) {
this.$set(this, 'reuseActivityOptions', resp.data || [])
} else {
this.$message.error(resp.msg)
}
}).fail(() => {
this.$message.error('历史活动列表加载失败,请重新打开创建活动')
})
},
// JSON 列可能返回字符串、数组或空值,统一转成独立数组,避免修改来源数据。
reuseArray(value) {
let data = value
try {
data = typeof value === 'string' ? JSON.parse(value || '[]') : value
} catch (error) {
this.$message.warning('历史活动存在无法读取的配置,请检查并重新填写')
return []
}
return Array.isArray(data) ? clone(data) : []
},
// id 为来源活动ID;响应包含活动字段和 schoolEvents,不携带旧主键进入新增保存。
reuseActivityChange(id) {
const requestId = ++this.reuseRequestId
this.$set(this, 'reuseReady', false)
if (!id) {
this.$set(this, 'reuseLoading', false)
this.openAdd()
return
}
this.$set(this, 'reuseLoading', true)
return $.post('/platform/activity/school/info/mange/findReuseActivity', {id}).then((resp) => {
if (requestId !== this.reuseRequestId) return
if (resp.code !== 0) {
this.$message.error(resp.msg)
return
}
const form = clone(resp.data)
// 项目列表及编号按沿用活动的实际类型加载,旧日期要求重新填写。
return $.when(
$.post('/platform/activity/common/getEvents', {type: form.applyType}),
$.post('/platform/activity/apply/generateActivityCode', {activity_type: form.activityLevel}),
$.post('/platform/vi/common/getClubsByRole')
).then((eventResponse, codeResponse, clubResponse) => {
if (requestId !== this.reuseRequestId) return
// 当前项目使用 jQuery 1.x;when 返回每个请求的 [data, status, xhr]。
const responses = [eventResponse[0], codeResponse[0], clubResponse[0]]
const failed = responses.find((response) => response.code !== 0)
if (failed) {
this.$message.error(failed.msg)
return
}
const events = responses[0].data || []
const selectedIds = this.reuseArray(form.eventsIds)
const availableIds = selectedIds.filter((eventId) => events.some((event) => event.id === eventId))
if (availableIds.length !== selectedIds.length) {
this.$message.warning('历史活动中已停用或不存在的项目未沿用,请检查活动项目')
}
;['applyWay', 'unitSponsor', 'undertakeUnit', 'unitJointly', 'image'].forEach((key) => {
this.$set(form, key, this.reuseArray(form[key]))
})
const configs = (form.schoolEvents || []).filter((event) => availableIds.includes(event.eventId)).map((event) => {
const config = clone(event)
this.$set(config, 'id', null)
this.$set(config, 'activityId', null)
this.$set(config, 'unionLimit', this.reuseArray(config.unionLimit))
this.$set(config, 'ageDate', config.startAgeDate ? [config.startAgeDate, config.endAgeDate] : [])
return config
})
;['id', 'foundDate', 'applyStartTime', 'applyEndTime', 'startTime', 'endTime'].forEach((key) => this.$set(form, key, null))
this.$set(form, 'schoolEvents', [])
this.$set(form, 'close', false)
this.$set(form, 'activityCode', responses[1].data)
this.$set(form, 'activityLevel', Number(form.activityLevel))
this.$set(form, 'activityGroupId', form.activityGroupId ? Number(form.activityGroupId) : null)
this.$set(form, 'applyTime', [])
this.$set(form, 'time', [])
this.$set(form, 'eventsIds', availableIds)
this.$set(this, 'events', events)
this.$set(this, 'clubOptions', responses[2].data || [])
this.$set(this, 'form', form)
this.$set(this, 'eventsIds', [])
this.$set(this, 'checkedEventInfo', configs)
this.$set(this, 'checkedEventInfo2', clone(configs))
this.$set(this, 'eventForm', {})
this.$set(this, 'checkedEvents', [])
this.$set(this, 'rightChecked', [])
this.$set(this, 'reuseReady', true)
this.$nextTick(() => {
this.initEditor()
if (this.$refs.activityForm) this.$refs.activityForm.clearValidate()
})
})
}).fail(() => {
if (requestId === this.reuseRequestId) {
this.$message.error('沿用活动加载失败,请重新选择活动')
}
}).always(() => {
if (requestId === this.reuseRequestId) this.$set(this, 'reuseLoading', false)
})
},
// 合并当前所选项目与已沿用配置;新增选择使用原有默认值,已移除项目不再提交。
buildReuseEvents() {
const selectedIds = this.form.eventsIds || []
return this.events.filter((event) => selectedIds.includes(event.id)).map((event) => {
const existing = this.checkedEventInfo.find((config) => config.eventId === event.id)
const config = existing ? clone(existing) : {
eventId: event.id, projectType: event.projectType, leanderNum: 0, coachNum: 0,
athletesMaxNum: event.projectType === '1' ? 3 : null,
restrictRegNumber: event.projectType === '1' ? 2 : null,
restrictTotalTeam: 1, substituteNum: 0, applyUserModel: 1
}
this.$set(config, 'id', null)
this.$set(config, 'activityId', null)
this.$set(config, 'startAgeDate', config.ageDate && config.ageDate.length ? config.ageDate[0] : null)
this.$set(config, 'endAgeDate', config.ageDate && config.ageDate.length ? config.ageDate[1] : null)
return config
})
},
// isSave 区分保存草稿和提交;草稿跳过字段校验,提交仍校验报名、活动及项目配置。
doReuseAdd(isSave) {
if (this.formLoading || this.reuseLoading) return
if (!this.reuseReady) {
this.$message.warning('请重新选择并加载沿用活动')
return
}
const save = () => {
const form = clone(this.form)
if (!isSave) {
if (!form.applyTime || form.applyTime.length !== 2 || !form.applyTime[0] || !form.applyTime[1]) {
this.$message.warning('请填写报名日期')
return
}
if (!form.time || form.time.length !== 2 || !form.time[0] || !form.time[1]) {
this.$message.warning('请填写活动时间')
return
}
if (moment(form.time[0]).valueOf() <= moment(form.applyTime[0]).valueOf()) {
this.$message.error('活动时间必须晚于报名时间')
return
}
if (!form.eventsIds || !form.eventsIds.length) {
this.$message.warning('请选择活动项目')
return
}
if (this.getArrDifference(form.unitJointly || [], form.unitSponsor || []).length === 0) {
this.$message.error('协办单位里存在主办单位')
return
}
}
this.$set(form, 'id', null)
this.$set(form, 'isSave', isSave ? 1 : 0)
// 草稿可没有报名和活动时间,缺失时明确传 null,避免复制旧活动日期。
this.$set(form, 'applyStartTime', form.applyTime && form.applyTime[0] && form.applyTime[1] ? form.applyTime[0] : null)
this.$set(form, 'applyEndTime', form.applyTime && form.applyTime[0] && form.applyTime[1] ? form.applyTime[1] : null)
this.$set(form, 'startTime', form.time && form.time[0] && form.time[1] ? form.time[0] : null)
this.$set(form, 'endTime', form.time && form.time[0] && form.time[1] ? form.time[1] : null)
this.$set(this, 'formLoading', true)
const loading = this.$loading({lock: true, text: isSave ? '正在保存...' : '正在提交...', background: COVER_LAYER_COLOR})
const request = $.post('/platform/activity/school/info/mange/doReuseAdd', {
sourceId: this.reuseActivityId, data: JSON.stringify(form), events: JSON.stringify(this.buildReuseEvents())
})
// jQuery 1.x 的 then 回调抛错后可能中断后续回调;直接监听请求结束以关闭遮罩。
request.always(() => {
this.$set(this, 'formLoading', false)
loading.close()
})
return request.then((resp) => {
if (resp.code !== 0) {
this.$message.error(resp.msg)
return
}
if (isSave) {
this.$message.success('保存成功')
window.location.href = '/platform/activity/school/info/mange'
return
}
this.$set(this.form, 'id', resp.data)
this.$set(this, 'reuseActivityId', null)
this.$emit('flip')
}, () => {
this.$message.error('请求失败,请检查活动列表后重试')
})
}
if (isSave) {
if (this.$refs.activityForm) this.$refs.activityForm.clearValidate()
return save()
}
if (this.$refs.activityForm) {
this.$refs.activityForm.validate((valid) => {
if (valid) save()
})
} else {
return save()
}
},
async applyUserModelInput(val) {
if (val === 2) {
if (!this.eventForm.unionLimit || this.eventForm.unionLimit.length === 0) {
@@ -871,6 +1089,11 @@ module.exports = {
});
},
next() {
if (this.reuseLoading || this.formLoading) return
if (this.reuseActivityId && !this.reuseReady) {
this.$message.warning('请重新选择并加载沿用活动')
return
}
this.$refs["activityForm"].validate(async (valid) => {
const form = clone(this.form)
if (!valid) {
@@ -883,7 +1106,8 @@ module.exports = {
return
}
if (moment(this.form.time[0]).valueOf() <= moment(this.form.applyTime[0]).valueOf()) {
// 活动时间可在保存草稿后补填;已填写时仍需晚于报名开始时间。
if (this.form.time && this.form.time[0] && this.form.time[1] && moment(this.form.time[0]).valueOf() <= moment(this.form.applyTime[0]).valueOf()) {
this.$notify.error({title: '错误', message: '活动时间必须晚于报名时间'});
return
}
@@ -917,7 +1141,13 @@ module.exports = {
this.eventForm.checkedEventId = this.checkedEvents[0].id
this.projectType = this.checkedEvents[0].projectType
if (!form.id || this.checkedEventInfo2.length <= 0) {
if (this.reuseActivityId && !form.id) {
// 新活动也保留沿用配置,不能因尚无活动ID而恢复为默认人数。
const allItems = this.checkedEventInfo.find((event) => event.eventId === 'allItems')
const configs = this.buildReuseEvents()
if (allItems) configs.unshift(clone(allItems))
this.$set(this, 'checkedEventInfo', configs)
} else if (!form.id || this.checkedEventInfo2.length <= 0) {
this.eventForm = {
checkedEventId: this.checkedEvents[0].id,
leanderNum: 0,
@@ -959,6 +1189,14 @@ module.exports = {
}
,
async openAdd() {
this.reuseRequestId++
this.$set(this, 'reuseLoading', false)
this.$set(this, 'reuseReady', false)
this.$set(this, 'reuseActivityId', null)
this.$set(this, 'checkedEventInfo', [])
this.$set(this, 'checkedEventInfo2', [])
this.$set(this, 'eventsIds', [])
this.getReuseActivityOptions()
this.events = await activity.getEvents(2)
this.planList = await activity.getSchoolActivityPlan(this.form.activityLevel)
await this.getActivityGroup()
@@ -977,6 +1215,7 @@ module.exports = {
applyWay: [2],
eventsIds: [],
unitJointly: [],
undertakeUnit: [],
applyType: 2,
activityCode: data,
activityLevel: 40001,
@@ -989,6 +1228,9 @@ module.exports = {
return data;
},
async openEdit(row, flag) {
this.reuseRequestId++
this.$set(this, 'reuseLoading', false)
this.$set(this, 'reuseActivityId', null)
this.active = flag ? 0 : this.active
await this.applyTypeChange(row.applyType)
this.planList = await activity.getSchoolActivityPlan(1)
@@ -1002,11 +1244,11 @@ module.exports = {
this.clubOptions = await getClubsByRole()
this.eventsIds = clone(JSON.parse(v.eventsIds))
v.applyTime = [v.applyStartTime, v.applyEndTime]
v.time = [v.startTime, v.endTime]
this.$set(v, 'time', v.startTime && v.endTime ? [v.startTime, v.endTime] : null)
v.eventsIds = JSON.parse(v.eventsIds)
v.applyWay = JSON.parse(v.applyWay)
v.unitSponsor = JSON.parse(v.unitSponsor)
v.undertakeUnit = JSON.parse(v.undertakeUnit)
v.undertakeUnit = this.reuseArray(v.undertakeUnit)
v.unitJointly = JSON.parse(v.unitJointly)
v.activityGroupId = parseInt(v.activityGroupId)
v.activityLevel = parseInt(v.activityLevel)
@@ -1075,6 +1317,14 @@ module.exports = {
}
,
async operate() {
if (this.reuseLoading || this.formLoading) return
if (this.reuseActivityId && !this.form.id) return this.doReuseAdd(false)
// 提交会开放活动,必须先补齐活动时间;仅保存草稿允许留空。
if (!this.form.time || this.form.time.length !== 2 || !this.form.time[0] || !this.form.time[1]) {
this.$message.warning('请填写活动时间后再提交')
return
}
const deleteEventIds = this.eventsIds.filter(v => !this.form.eventsIds.includes(v))
const {data} = await $.get(loc() + "/getEventApply", {
@@ -1093,7 +1343,11 @@ module.exports = {
await this.doOperate()
}
},
async doSave() {
doSave() {
if (this.reuseLoading || this.formLoading) return
// 保存草稿不触发表单必填校验;下一步和提交仍按各自规则处理。
if (this.$refs.activityForm) this.$refs.activityForm.clearValidate()
if (this.reuseActivityId && !this.form.id) return this.doReuseAdd(true)
let url = this.form.id ? "/doEdit" : "/doAdd"
this.checkedEventInfo.forEach(v => {
@@ -1115,25 +1369,32 @@ module.exports = {
const form = clone(this.form)
form.restrictMaxNum = form.leanderNum + form.coachNum + form.athletesMaxNum + form.substituteNum
form.restrictMinNum = form.athletesMaxNum
form.isSave = true
form.isSave = 1
const {time, applyTime} = form
if (applyTime && applyTime.length) {
if (applyTime && applyTime.length === 2 && applyTime[0] && applyTime[1]) {
form.applyStartTime = applyTime[0]
form.applyEndTime = applyTime[1]
form.applyTime = undefined
} else {
form.applyStartTime = null
form.applyEndTime = null
}
if (time && time.length) {
if (time && time.length === 2 && time[0] && time[1]) {
form.startTime = time[0]
form.endTime = time[1]
form.time = undefined
} else {
// 清空已有活动时间时显式传 null,避免编辑草稿时沿用旧值。
form.startTime = null
form.endTime = null
}
if (this.eventForm.checkedEventId == "allItems") {
events = events.filter(v => v.projectType == 1 && v.id != "allItems")
} else {
} else if (this.eventForm.checkedEventId) {
this.$set(this.eventForm, "activityId", this.form.id)
this.$set(this.eventForm, "eventId", this.eventForm.checkedEventId)
if (this.eventForm.ageDate && this.eventForm.ageDate.length > 0) {
@@ -1142,26 +1403,29 @@ module.exports = {
}
events = this.eventForm
}
this.$set(this, 'formLoading', true)
const loading = this.$loading({
lock: true,
text: '正在提交...',
text: '正在保存...',
spinner: 'el-icon-loading',
background: COVER_LAYER_COLOR
});
const resp = await $.post("/platform/activity/school/info/mange" + url, {
return $.post("/platform/activity/school/info/mange" + url, {
data: JSON.stringify(form),
events: JSON.stringify(events)
})
closeLoadingFunc(loading, resp, async (data) => {
this.$emit('flush')
if (!this.form.id) form.id = data
await this.openEdit(form, false)
}, (data, msg) => {
this.$message.error(msg);
}).then((resp) => {
if (resp.code === 0) {
this.$message.success('保存成功')
window.location.href = '/platform/activity/school/info/mange'
} else {
this.$message.error(resp.msg)
}
}, () => {
this.$message.error('保存失败,请检查活动列表后重试')
}).always(() => {
this.$set(this, 'formLoading', false)
loading.close()
})
},
examineTrans(form) {
@@ -111,6 +111,8 @@ module.exports = {
{prop: 'loginname', label: '工号'},
{prop: 'mobile', label: '电话'},
{prop: 'sex', label: '性别'},
{prop: 'birthday', label: '出生年月'},
{prop: 'unitname', label: '单位'},
{prop: 'identity', label: '身份', sortable: true},
// {prop: 'divisionLevelLeadership', label: '是否处级领导', sortable: true},
{prop: 'status', label: '状态', sortable: true},
@@ -331,6 +331,8 @@
prop="mobile" header-align="center" align="center"
label="电话">
</el-table-column>
<el-table-column prop="birthday" header-align="center" align="center"
label="出生年月"></el-table-column>
<el-table-column
prop="sex" header-align="center" align="center" sortable
@@ -235,6 +235,36 @@
</el-form>
</el-tab-pane>
<el-tab-pane v-if="hasPane(6)&&viewData.ecr" label="工会常务副主席审核信息" name="6">
<el-form label-width="130px" label-suffix=":" style="padding: 20px 0">
<vi-title title="审核信息"></vi-title>
<el-row gutter="20">
<el-col :span="12">
<el-form-item label="审核人员">
{{ viewData.ecr.username }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="审核时间">
{{ viewData.ecr.time }}
</el-form-item>
</el-col>
</el-row>
<el-form-item label="审核意见">
<div class="textarea-div">{{ viewData.ecr.opinion ? viewData.ecr.opinion : '暂无' }}</div>
</el-form-item>
<el-form-item label="签&emsp;&emsp;字">
<el-image v-if="viewData.ecr.sign"
style="width: 300px; height: 100px"
:src="viewData.ecr.sign"
fit="contain"></el-image>
<span v-else>暂无</span>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane v-if="handle" :label="label" name="999">
<slot name="handle"></slot>
</el-tab-pane>
@@ -257,7 +287,7 @@ module.exports = {
},
panes: {
type: Array,
default: []
default: () => []
},
},
filters: {
@@ -278,27 +308,32 @@ module.exports = {
watch: {},
methods: {
hasPane(name) {
if (!this.handle) {
// 未指定页签范围时展示所有已有审核记录;显式传入范围时保持原有限制。
if (!this.handle || this.panes.length === 0) {
return true
}
return this.panes.includes(name)
},
async getInfo(id) {
this.loading = true
const {data, code, msg} = await $.get("/platform/fw/condolence/findOne", {id})
this.loading = false
if (data) {
this.activeName = this.handle ? "999" : "1"
condolence.toFormData(data)
this.viewData = data
} else {
this.viewData = {}
this.$message({
message: "获取信息失败",
type: 'error'
});
}
/**
* id 为慰问申请 ID;接口 data 为申请详情,ucr/fr/vcr/ecr 为各节点审核记录对象。
* 无记录时对应页签隐藏;请求结束后统一解除加载状态。
*/
getInfo(id) {
this.$set(this, 'loading', true)
this.$set(this, 'viewData', {})
return $.post('/platform/fw/condolence/findOne', {id})
.then((res) => {
if (res.code === 0 && res.data) {
condolence.toFormData(res.data)
this.$set(this, 'activeName', this.handle ? '999' : '1')
this.$set(this, 'viewData', res.data)
} else {
this.$message.error('获取信息失败')
}
})
.always(() => {
this.$set(this, 'loading', false)
})
},
},
created() {
@@ -0,0 +1,246 @@
<template>
<el-dialog :title="title" :visible.sync="visible" width="88%" top="5vh"
append-to-body :close-on-click-modal="false">
<el-form :inline="true" :model="form" size="small" class="dashboard-detail-filter">
<el-form-item label="统计年度" v-if="detailType === 'member'">
<el-date-picker v-model="form.year" type="year" value-format="yyyy"
clearable placeholder="选择年度" @change="search"></el-date-picker>
</el-form-item>
<el-form-item label="时间">
<el-date-picker v-model="form.dateRange" type="daterange" value-format="yyyy-MM-dd"
range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
clearable @change="search"></el-date-picker>
</el-form-item>
<el-form-item label="所属工会">
<el-select v-model="form.unionId" clearable filterable placeholder="所属工会"
@change="flushUnits(false)">
<el-option v-for="item in unions" :key="item.id"
:label="item.unionname" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="所属单位">
<el-select v-model="form.unitId" clearable filterable placeholder="所属单位" @change="search">
<el-option v-for="item in units" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="人员类型">
<el-select v-model="form.personType" clearable placeholder="人员类型" @change="search">
<el-option v-for="item in personTypeOptions" :key="item.code"
:label="item.name" :value="item.code"></el-option>
</el-select>
</el-form-item>
<el-form-item label="在职状态">
<el-select v-model="form.userState" clearable placeholder="在职状态" @change="search">
<el-option v-for="item in userStateOptions" :key="item.code"
:label="item.name" :value="item.code"></el-option>
</el-select>
</el-form-item>
<el-form-item label="性别">
<el-select v-model="form.sex" clearable placeholder="性别" @change="search">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</el-form-item>
<template v-if="detailType === 'medical'">
<el-form-item label="补助类型">
<el-select v-model="form.medicalType" clearable placeholder="补助类型" @change="search">
<el-option v-for="item in medicalTypeOptions" :key="item.value"
:label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="申请状态">
<el-select v-model="form.stateId" clearable placeholder="申请状态" @change="search">
<el-option v-for="item in medicalStates" :key="item.value"
:label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
</template>
<el-form-item label="人员">
<el-input v-model="form.searchKeyword" clearable placeholder="姓名或工号"
@keyup.enter.native="search"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="search">查询</el-button>
<el-button icon="el-icon-refresh" @click="reset">重置</el-button>
<el-button type="success" icon="el-icon-download" @click="exportData">导出</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" border stripe height="calc(75vh - 170px)"
v-loading="loading" style="width: 100%">
<el-table-column type="index" label="序号" width="70" align="center"
:index="indexMethod"></el-table-column>
<el-table-column prop="loginname" label="工号" min-width="110" show-overflow-tooltip></el-table-column>
<el-table-column prop="username" label="姓名" min-width="100" show-overflow-tooltip></el-table-column>
<el-table-column prop="sex" label="性别" width="70" align="center"></el-table-column>
<el-table-column prop="persontype" label="人员类型" min-width="110" show-overflow-tooltip></el-table-column>
<el-table-column prop="userstate" label="在职状态" min-width="100" show-overflow-tooltip></el-table-column>
<el-table-column prop="unionname" label="所属工会" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column prop="unitname" label="所属单位" min-width="160" show-overflow-tooltip></el-table-column>
<template v-if="detailType === 'medical'">
<el-table-column prop="typename" label="补助类型" min-width="110"></el-table-column>
<el-table-column prop="detailtime" label="申请时间" min-width="160"></el-table-column>
<el-table-column prop="state_name" label="申请状态" min-width="120"></el-table-column>
<el-table-column prop="total_grant" label="补助金额" min-width="110" align="right"></el-table-column>
</template>
<el-table-column v-else prop="detailtime" label="加入时间" min-width="160"></el-table-column>
</el-table>
<el-pagination class="dashboard-detail-pagination"
background
layout="total, sizes, prev, pager, next, jumper"
:current-page="pageForm.pageNumber"
:page-size="pageForm.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pageForm.totalCount"
@current-change="pageChange"
@size-change="pageSizeChange"></el-pagination>
</el-dialog>
</template>
<script>
module.exports = {
props: {
baseUrl: {
type: String,
required: true
}
},
data() {
return {
visible: false,
loading: false,
title: '指标明细',
detailType: 'member',
form: this.emptyForm(),
initialForm: {},
tableData: [],
unions: [],
units: [],
personTypeOptions: [],
userStateOptions: [],
medicalStates: [],
medicalTypeOptions: [
{label: '医疗补助', value: 1},
{label: '重疾补助', value: 2},
{label: '特殊门诊补助', value: 3}
],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0
}
}
},
methods: {
emptyForm() {
return {
detailType: 'member',
year: '',
dateRange: [],
unionId: '',
unitId: '',
personType: '',
userState: '',
sex: '',
medicalType: '',
stateId: '',
searchKeyword: ''
}
},
async open(title, detailType, filters) {
this.title = title || '指标明细'
this.detailType = detailType || 'member'
const form = this.emptyForm()
form.detailType = this.detailType
Object.assign(form, filters || {})
if (form.startTime || form.endTime) {
form.dateRange = [form.startTime || '', form.endTime || '']
}
this.form = form
this.initialForm = clone(form)
this.visible = true
await this.flushUnits(true)
this.search()
},
buildParams() {
const params = clone(this.form)
params.detailType = this.detailType
params.startTime = params.dateRange && params.dateRange.length ? params.dateRange[0] : ''
params.endTime = params.dateRange && params.dateRange.length ? params.dateRange[1] : ''
delete params.dateRange
return params
},
async loadData() {
this.loading = true
const params = Object.assign(this.buildParams(), {
pageNumber: this.pageForm.pageNumber,
pageSize: this.pageForm.pageSize
})
const resp = await $.get(this.baseUrl + '/detailPage', params)
requestLaterFun(resp, data => {
this.tableData = data.list || []
this.pageForm.totalCount = data.totalCount || 0
})
this.loading = false
},
search() {
if (!this.visible) return
this.pageForm.pageNumber = 1
this.loadData()
},
async reset() {
this.form = clone(this.initialForm)
await this.flushUnits(true)
this.search()
},
async flushUnits(preserveUnit) {
if (!preserveUnit) {
this.$set(this.form, 'unitId', '')
}
this.units = await getUnits(this.form.unionId)
if (!preserveUnit) {
this.search()
}
},
pageChange(pageNumber) {
this.pageForm.pageNumber = pageNumber
this.loadData()
},
pageSizeChange(pageSize) {
this.pageForm.pageSize = pageSize
this.pageForm.pageNumber = 1
this.loadData()
},
indexMethod(index) {
return (this.pageForm.pageNumber - 1) * this.pageForm.pageSize + index + 1
},
exportData() {
window.open(this.baseUrl + '/exportDetail?' + $.param(this.buildParams()))
}
},
async created() {
this.unions = await getUnions(null)
this.personTypeOptions = await getDictOptions('UserType')
this.userStateOptions = await getDictOptions('UserState')
const resp = await $.get(this.baseUrl + '/detailOptions')
requestLaterFun(resp, data => {
this.medicalStates = data.medicalStates || []
})
}
}
</script>
<style scoped>
.dashboard-detail-filter {
margin-bottom: 4px;
}
.dashboard-detail-filter .el-form-item {
margin-bottom: 12px;
}
.dashboard-detail-pagination {
margin-top: 15px;
text-align: right;
}
</style>
+115 -19
View File
@@ -223,9 +223,12 @@ layout("/mobile/platform.html"){
roles: [],
active: 1,
needCount: 0,
needRequestId: 0,
needItems: [],
activeName: '0',
completedItems: [],
completedLoading: false,
completedRequestId: 0,
}
},
methods: {
@@ -244,7 +247,8 @@ layout("/mobile/platform.html"){
location.href = o.mobileHref
},
toHref(o) {
location.href = o.mobileUrl + '?audit=1';
// 详情链接已携带申请 ID,保留查询参数。
location.href = o.mobileUrl;
},
/**
* 入学登记待办
@@ -256,20 +260,111 @@ layout("/mobile/platform.html"){
this.needItems = this.needItems.concat(data.filter(v => v.number > 0))
}
},*/
async getNeedItems() {
const {data, code} = await $.get('/platform/needItems/getNeedItems')
if (code === 0) {
const obj = data.filter(v => {
return v.mobileHref != null && v.count > 0
/**
* requestId 为本轮刷新序号,忽略旧请求,避免返回页面或切换页签时覆盖新数据。
* 通用接口 data 为待办数组,使用 count 和 mobileHref;完成后继续加载慰问待办。
*/
getNeedItems(requestId) {
return $.post('/platform/needItems/getNeedItems')
.then((res) => {
if (requestId === this.needRequestId && res.code === 0) {
this.$set(this, 'needItems', (res.data || []).filter(v => v.mobileHref && v.count > 0
&& v.mobileHref.split('?')[0] !== '/mobile/member/audit'
&& !['/platform/member/apply/audit/union', '/platform/member/apply/audit/school'].includes((v.href || '').split('?')[0])))
}
})
.always(() => {
if (requestId === this.needRequestId) {
this.getCondolenceAgenda(requestId)
}
})
this.needItems = obj
}
},
async getCompletes() {
const {data, code} = await $.get('/platform/sys/completed/getCompletes')
if (code === 0) {
this.completedItems = data;
}
/**
* 复用 PC 慰问待办的菜单权限和统计口径,将 label/number 转为手机模板的 name/count。
* 仅映射手机端已有审核页面的三个节点,历史常务副主席节点不提供手机办理入口。
*/
getCondolenceAgenda(requestId) {
const auditUrls = [
'/platform/fw/condolence/ucr/union',
'/platform/fw/condolence/fr',
'/platform/fw/condolence/vcr/school'
]
return $.post('/platform/condolence/agent/getAgenda')
.then((res) => {
if (requestId !== this.needRequestId || res.code !== 0) {
return
}
const items = (res.data || []).filter(v => auditUrls.includes(v.url) && v.number > 0)
.map(v => ({
title: v.title,
name: v.label,
count: v.number,
mobileHref: '/mobile/condolence/audit'
}))
this.$set(this, 'needItems', this.needItems.concat(items))
})
.always(() => {
if (requestId === this.needRequestId) {
this.getMemberApplyAgenda(requestId)
}
})
},
/**
* 无需业务参数;后端按会话权限返回今年会员入会节点,字段与首页待办卡片一致。
* 专用数据替代通用配置中的同业务卡片,避免双计数;失败不影响已加载的慰问待办。
*/
getMemberApplyAgenda(requestId) {
return $.post('/platform/member/agenda/getMobileAgenda')
.then((res) => {
if (requestId !== this.needRequestId) return
if (res.code === 0) {
const items = (res.data || []).filter(v => v.count > 0 && v.mobileHref)
this.$set(this, 'needItems', this.needItems.concat(items))
} else {
vant.Toast.fail('会员入会待办加载失败,请重试')
}
}, () => {
if (requestId === this.needRequestId) vant.Toast.fail('会员入会待办加载失败,请重试')
})
.always(() => {
if (requestId === this.needRequestId) this.$set(this, 'needCount', this.needItems.length)
})
},
/**
* 两个接口均无需传办理人,按当前会话返回本人今年已办。
* 各业务独立合并;全部请求结束后关闭 loading,旧刷新结果不得覆盖当前列表。
*/
getCompletes() {
const requestId = this.completedRequestId + 1
this.$set(this, 'completedRequestId', requestId)
this.$set(this, 'completedItems', [])
this.$set(this, 'completedLoading', true)
const sources = [
{url: '/platform/condolence/agent/getCompletes', label: '慰问'},
{url: '/platform/member/agenda/getCompletes', label: '会员入会'}
]
let remaining = sources.length
const requests = sources.map(source => $.post(source.url)
.then((res) => {
if (requestId !== this.completedRequestId) return
if (res.code === 0) {
const items = this.completedItems.concat(res.data || [])
// 现有日期字段统一交给 moment 解析,兼容日期字符串和时间戳。
items.sort((a, b) => (moment(b.auditTime).valueOf() || 0) - (moment(a.auditTime).valueOf() || 0))
this.$set(this, 'completedItems', items)
} else {
vant.Toast.fail(source.label + '已办加载失败,请重试')
}
}, () => {
if (requestId === this.completedRequestId) vant.Toast.fail(source.label + '已办加载失败,请重试')
})
.always(() => {
remaining--
if (requestId === this.completedRequestId && remaining === 0) {
this.$set(this, 'completedLoading', false)
}
}))
return $.when.apply($, requests)
},
/**
* 会员个人核对待办
@@ -291,12 +386,13 @@ layout("/mobile/platform.html"){
this.needItems = this.needItems.concat(data.filter(v => v.count > 0))
}
},
async initData() {
await this.getNeedItems();
// await this.getMemberCheckSelfAgenda()
// await this.getWelfareCheckSelfAgenda()
// await this.getCompletes();
this.needCount = this.needItems.length
initData() {
this.getCompletes()
// 每轮重新合并通用、慰问和会员入会待办,防止重复追加;刷新序号隔离尚未完成的旧请求。
this.$set(this, 'needRequestId', this.needRequestId + 1)
this.$set(this, 'needItems', [])
this.$set(this, 'needCount', 0)
return this.getNeedItems(this.needRequestId)
},
async setNeedItems() {
const {data, code} = await $.get('/platform/needItems/getNeedItems')
@@ -59,9 +59,10 @@ layout("/mobile/platform.html"){
{text: '已审核', value: true},
],
handle: true,
queryVersion: 0,
pageForm: {
year: new Date().getFullYear(),
isAudit: false,
year: GetQueryString("isAudit") === "true" ? null : new Date().getFullYear(),
isAudit: GetQueryString("isAudit") === "true",
},
}
},
@@ -70,6 +71,7 @@ layout("/mobile/platform.html"){
},
methods: {
yearChange() {
this.queryVersion++
this.pageForm.pageNumber = 1
this.tableData = []
this.finished = false
@@ -77,40 +79,32 @@ layout("/mobile/platform.html"){
},
onLoad() {
this.loading = true
$.post(loc() + '/pageData', this.pageForm).then(res => {
// 捕获本次筛选条件,筛选切换后忽略旧请求,避免串页。
const requestKey = this.queryVersion || 0
$.post('/mobile/member/audit/pageData', Object.assign({}, this.pageForm)).then(res => {
if (requestKey !== (this.queryVersion || 0)) return
if (res.code === 0) {
this.tableData = this.tableData.concat(res.data.list)
if (this.tableData.length === res.data.totalCount) {
this.finished = true
} else {
this.pageForm.pageNumber++
}
this.finished = this.tableData.length >= res.data.totalCount || res.data.list.length === 0
if (!this.finished) this.pageForm.pageNumber++
} else {
this.finished = true
this.$toast.fail(res.msg || '加载失败')
}
this.loading = false
}).fail(() => {
if (requestKey !== (this.queryVersion || 0)) return
this.finished = true
this.$toast.fail('加载失败,请重新筛选后重试')
}).always(() => {
if (requestKey === (this.queryVersion || 0)) this.loading = false
})
},
/* pageData() {
this.mLoading = true
setTimeout(async () => {
$.get(loc() + '/pageData', this.pageForm, (res) => {
if (res.code === 0) {
this.tableData = this.tableData.concat(res.data.list)
if (this.tableData.length === res.data.totalCount) {
this.finished = true
} else {
this.pageForm.pageNumber++
}
}
})
this.mLoading = false
}, 600)
},*/
openView(o) {
window.location.href = '/mobile/member/audit/findOne?stateId=' + o.stateId + '&record_id=' + o.id + '&user_id=' + o.userId
window.location.href = '/mobile/member/audit/findOne?stateId=' + o.stateId + '&record_id=' + o.id + '&user_id=' + o.userId + '&done=' + this.pageForm.isAudit
},
},
async created() {
created() {
this.yearList = [{text: '全部年份', value: null}]
let nowYear = new Date().getFullYear()
for (let i = nowYear; i >= nowYear - 9; i--) {
this.yearList.push({text: i, value: i})
@@ -0,0 +1,194 @@
<!--#
layout("/mobile/platform.html"){
#-->
<style>
.member-query-header {
position: sticky;
top: 0;
z-index: 10;
background-color: #fff;
}
.member-query-list {
padding: 10px 0 20px;
}
.member-query-count {
padding: 0 16px 10px;
color: #969799;
font-size: 13px;
}
.member-query-detail {
height: 85%;
padding-bottom: env(safe-area-inset-bottom);
box-sizing: border-box;
}
.member-query-detail .van-cell__value,
.member-query-list .van-cell__value {
overflow-wrap: anywhere;
white-space: pre-wrap;
}
.member-query-loading {
padding: 40px 0;
text-align: center;
}
</style>
<div id="app" v-cloak>
<div class="member-query-header">
<van-nav-bar title="会员查询" left-arrow placeholder fixed
@click-left="location.replace('/mobile/index')"></van-nav-bar>
<form action="/mobile/member/query" @submit.prevent="doSearch">
<van-search v-model="keyword" placeholder="请输入姓名或工号" label="会员" show-action
@search="doSearch" @clear="doSearch">
<template #action>
<van-button type="primary" size="small" native-type="button" @click="doSearch">搜索</van-button>
</template>
</van-search>
</form>
</div>
<div class="member-query-list">
<div v-if="tableData.length" class="member-query-count">共 {{pageForm.totalCount}} 位会员</div>
<van-list v-model="loading" :finished="finished" :error.sync="loadError"
error-text="加载失败,点击重试" :finished-text="tableData.length ? '没有更多了' : ''"
:immediate-check="false" @load="onLoad">
<div v-for="o in tableData" :key="o.id" class="list-card">
<van-cell-group inset>
<van-cell :border="false" title="姓  名">{{o.username || '—'}}</van-cell>
<van-cell :border="false" title="工  号">{{o.loginname || '—'}}</van-cell>
<van-cell :border="false" title="所在工会">{{o.unionname || '—'}}</van-cell>
<van-cell :border="false" title="所在单位">{{o.unitname || '—'}}</van-cell>
<van-cell :border="false" title="人员类型">{{o.personType || '—'}}</van-cell>
</van-cell-group>
<van-cell title="查看会员详情" is-link clickable @click="openView(o)"></van-cell>
</div>
</van-list>
<van-empty v-if="!loading && !loadError && finished && tableData.length === 0"
image="/none.svg" description="暂无符合条件的会员"></van-empty>
</div>
<van-popup v-model="showDetail" position="bottom" round class="member-query-detail">
<van-nav-bar title="会员详情" left-arrow @click-left="showDetail = false"></van-nav-bar>
<div v-if="detailLoading" class="member-query-loading">
<van-loading size="24px" vertical>加载中...</van-loading>
</div>
<van-empty v-else-if="detailError" description="详情加载失败">
<van-button type="primary" size="small" @click="openView(selectedMember)">重试</van-button>
</van-empty>
<van-cell-group v-else-if="memberInfo">
<van-cell title="姓名" :value="memberInfo.username || '—'"></van-cell>
<van-cell title="性别" :value="memberInfo.sex || '—'"></van-cell>
<van-cell title="年龄" :value="memberInfo.age == null ? '—' : memberInfo.age"></van-cell>
<van-cell title="民族" :value="memberInfo.nation || '—'"></van-cell>
<van-cell title="学历" :value="memberInfo.education || '—'"></van-cell>
<van-cell title="政治面貌" :value="memberInfo.political || '—'"></van-cell>
<van-cell title="职称" :value="memberInfo.jobTitle || '—'"></van-cell>
<van-cell title="职务" :value="memberInfo.position || '—'"></van-cell>
<van-cell title="所在单位" :value="memberInfo.unitname || '—'"></van-cell>
<van-cell title="个人简历" :value="memberInfo.vita || '—'"></van-cell>
</van-cell-group>
<van-empty v-else description="暂无会员详情"></van-empty>
</van-popup>
</div>
<script>
// 复用移动端会员接口,在查询页内查看详情,保持与查询入口一致的角色权限。
const vue = new Vue({
el: '#app',
mixins: [mobileMixins],
data() {
return {
keyword: '',
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: ''
},
loadError: false,
requestId: 0,
pending: false,
showDetail: false,
detailLoading: false,
detailError: false,
detailRequestId: 0,
selectedMember: null,
memberInfo: null
}
},
methods: {
doSearch() {
// 新搜索使上一轮分页请求失效,避免较慢的旧响应混入新结果。
this.requestId++
this.pending = false
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.pageForm.searchKeyword = this.keyword.trim()
this.tableData = []
this.finished = false
this.loadError = false
this.onLoad()
},
async onLoad() {
if (this.pending || this.finished) return
const requestId = this.requestId
this.pending = true
this.loading = true
this.loadError = false
try {
const res = await $.post('/mobile/member/getMemberData', Object.assign({}, this.pageForm))
if (requestId !== this.requestId) return
if (res.code !== 0 || !res.data || !Array.isArray(res.data.list)) {
throw new Error('会员查询失败')
}
this.tableData = this.tableData.concat(res.data.list)
this.pageForm.totalCount = res.data.totalCount
this.finished = res.data.list.length === 0 || this.tableData.length >= res.data.totalCount
if (!this.finished) this.pageForm.pageNumber++
} catch (e) {
if (requestId === this.requestId) this.loadError = true
} finally {
if (requestId === this.requestId) {
this.pending = false
this.loading = false
}
}
},
async openView(member) {
if (!member || !member.id) {
this.$toast.fail('缺少会员信息')
return
}
const requestId = ++this.detailRequestId
this.selectedMember = member
this.memberInfo = null
this.showDetail = true
this.detailLoading = true
this.detailError = false
try {
const res = await $.get('/mobile/member/viewInfo', {id: member.id})
if (requestId !== this.detailRequestId) return
if (res.code !== 0) throw new Error('会员详情加载失败')
this.memberInfo = res.data
} catch (e) {
if (requestId === this.detailRequestId) this.detailError = true
} finally {
if (requestId === this.detailRequestId) this.detailLoading = false
}
}
},
created() {
this.doSearch()
}
})
</script>
<!--#
}
#-->
@@ -26,7 +26,6 @@ layout("/mobile/platform.html"){
<van-field v-model="formData.auditTime" label="审核时间" readonly></van-field>
<van-field
required
v-model="formData.auditOpinion"
rows="1"
autosize
@@ -46,12 +45,12 @@ layout("/mobile/platform.html"){
<van-row gutter="20" style="margin-top: 30px;padding: 0 25px">
<van-col span="12">
<van-button :color="themeColor" plain block @click="doAudit(false)">拒 绝
<van-button :color="themeColor" plain block :loading="formLoading" :disabled="formLoading" @click="doAudit(false)">拒 绝
</van-button>
</van-col>
<van-col span="12">
<van-button :color="themeColor" block @click="doAudit(true)">通 过
<van-button :color="themeColor" block :loading="formLoading" :disabled="formLoading" @click="doAudit(true)">通 过
</van-button>
</van-col>
</van-row>
@@ -72,6 +71,7 @@ layout("/mobile/platform.html"){
data() {
return {
handle: true,
formLoading: false,
id: "",
formData: {},
}
@@ -81,25 +81,27 @@ layout("/mobile/platform.html"){
'mobile-sign': httpVueLoader('/components/sign/mobileSign.vue'),
},
methods: {
async doAudit(pass) {
if (!this.formData.auditOpinion) {
this.$toast.fail('请填写审核意见');
return
}
this.formData.auditPass = pass
const resp = await $.post("/platform/member/apply/audit/doAudit", this.formData)
requestLaterFun(resp, (data) => {
this.$toast.success('审核成功');
setTimeout(() => {
location.href = '/mobile/member/audit'
}, 1000)
}, () => {
this.$toast.fail('审核失败');
}, () => {
doAudit(pass) {
if (this.formLoading) return
// 与 PC 共用审核参数;提交前读取手写签名,避免仅提交历史签名。
this.$refs.signature.submitSign()
this.$set(this.formData, 'auditPass', pass)
this.formLoading = true
$.post('/platform/member/apply/audit/doAudit', this.formData).then(resp => {
requestLaterFun(resp, () => {
this.$toast.success('审核成功')
location.replace('/mobile/member/audit')
}, () => {
this.$toast.fail('审核失败')
}, () => {})
}).fail(() => {
this.$toast.fail('审核失败,请稍后重试')
}).always(() => {
this.formLoading = false
})
},
},
async created() {
created() {
const record_id = GetQueryString("record_id")
const user_id = GetQueryString("user_id")
setTimeout(() => {
@@ -26,7 +26,7 @@ layout("/mobile/platform.html"){
<van-field v-model="formData.auditTime" label="审核时间" readonly></van-field>
<van-field required
<van-field
v-model="formData.auditOpinion"
rows="1"
autosize
@@ -45,12 +45,12 @@ layout("/mobile/platform.html"){
<van-row gutter="20" style="margin-top: 30px;padding: 0 25px">
<van-col span="12">
<van-button :color="themeColor" plain block @click="doAudit(false)">拒 绝
<van-button :color="themeColor" plain block :loading="formLoading" :disabled="formLoading" @click="doAudit(false)">拒 绝
</van-button>
</van-col>
<van-col span="12">
<van-button :color="themeColor" block @click="doAudit(true)">通 过
<van-button :color="themeColor" block :loading="formLoading" :disabled="formLoading" @click="doAudit(true)">通 过
</van-button>
</van-col>
</van-row>
@@ -70,6 +70,7 @@ layout("/mobile/platform.html"){
data() {
return {
handle: true,
formLoading: false,
id: "",
formData: {},
}
@@ -80,25 +81,27 @@ layout("/mobile/platform.html"){
},
methods: {
async doAudit(pass) {
if (!this.formData.auditOpinion) {
this.$toast.fail('请填写审核意见');
return
}
this.formData.auditPass = pass
const resp = await $.post("/platform/member/apply/audit/doAudit", this.formData)
requestLaterFun(resp, (data) => {
this.$toast.success('审核成功');
setTimeout(() => {
location.href = '/mobile/member/audit'
}, 1000)
}, () => {
this.$toast.fail('审核失败');
}, () => {
doAudit(pass) {
if (this.formLoading) return
// 与 PC 共用审核参数;提交前读取手写签名,避免仅提交历史签名。
this.$refs.signature.submitSign()
this.$set(this.formData, 'auditPass', pass)
this.formLoading = true
$.post('/platform/member/apply/audit/doAudit', this.formData).then(resp => {
requestLaterFun(resp, () => {
this.$toast.success('审核成功')
location.replace('/mobile/member/audit')
}, () => {
this.$toast.fail('审核失败')
}, () => {})
}).fail(() => {
this.$toast.fail('审核失败,请稍后重试')
}).always(() => {
this.formLoading = false
})
},
},
async created() {
created() {
const record_id = GetQueryString("record_id")
const user_id = GetQueryString("user_id")
setTimeout(() => {
@@ -106,7 +106,7 @@ layout("/mobile/platform.html"){
@click-left="location.replace('/platform/mobile/trainSignUpActivity/trainList')"></van-nav-bar>
</van-sticky>
<van-image height="186" :src="CREATE_PREVIEW_URL(activity?.cover[0].id)"></van-image>
<van-image height="186" :src="activity.cover && activity.cover[0] ? CREATE_PREVIEW_URL(activity.cover[0].id) : ''"></van-image>
<div class="title">{{activity.activityName}}</div>
<van-tabs v-model:active="tabActive" shrink>
@@ -348,7 +348,7 @@ layout("/layouts/platform.html"){
v-if="row.applyWay.includes(2)">
项目报名
</el-dropdown-item>
<!--#if(@shiro.hasRole('sysadmin')||@shiro.hasRole('H04')||@shiro.hasRole('gh08')){#-->
<!--#if(@shiro.hasRole('sysadmin')||@shiro.hasRole('H04')||@shiro.hasRole('SportsActivityUnionAdmin')||@shiro.hasRole('gh08')){#-->
<el-dropdown-item :command="{type:'delete',row}">
删除报名人员
</el-dropdown-item>
@@ -450,6 +450,8 @@ layout("/layouts/platform.html"){
prop="mobile" header-align="center" align="center"
label="电话">
</el-table-column>
<el-table-column prop="birthday" header-align="center" align="center"
label="出生年月"></el-table-column>
<el-table-column
align="center" header-align="center" label="年龄"
@@ -641,6 +643,8 @@ layout("/layouts/platform.html"){
prop="mobile" header-align="center" align="center"
label="电话">
</el-table-column>
<el-table-column prop="birthday" header-align="center" align="center"
label="出生年月"></el-table-column>
<el-table-column
prop="sex" header-align="center" align="center"
@@ -714,7 +718,7 @@ layout("/layouts/platform.html"){
:visible.sync="dialogLeaderCoach"
width="60%"
>
<el-form :model="headData" ref="form" :rules="formRules" label-width="100px">
<el-form :model="headData" ref="leaderCoachForm" :rules="formRules" label-width="100px">
<el-row gutter="20">
<!-- <el-col :span="12">
<vi-title title="填写团长信息"></vi-title>
@@ -878,7 +882,7 @@ layout("/layouts/platform.html"){
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogLeaderCoach = false">取 消</el-button>
<el-button type="primary" plain @click="doLeaderCoach">确 认</el-button>
<el-button type="primary" plain :loading="formLoading" @click="doLeaderCoach">确 认</el-button>
</span>
</el-dialog>
@@ -902,62 +906,58 @@ layout("/layouts/platform.html"){
</el-select>
</el-form-item>
<el-form-item prop="username" label="姓&emsp;&emsp;名">
<el-select
:remote-method="querySearchAsync"
@change="addUserChange"
filterable
placeholder="请输入工号或者下姓名查询"
remote
reserve-keyword
style="width: 100%"
v-model="formData.userId">
<el-option
:key="item.id"
:label="item.username+'-'+item.loginname"
:value="item.id"
v-for="item in lsUserList">
</el-option>
</el-select>
<el-autocomplete style="width: 100%" maxlength="50" :trigger-on-focus="false"
placeholder="输入姓名或工号搜索,未匹配时直接填写姓名"
:fetch-suggestions="searchExternalUser" v-model="formData.username"
@input="changeExternalName" @select="selectExternalUser">
<template slot-scope="{ item }">{{item.username}}({{item.loginname}})</template>
</el-autocomplete>
</el-form-item>
<el-form-item prop="loginname" label="工&emsp;&emsp;号">
<el-input disabled maxlength="50" placeholder="请填写工号" v-model="formData.loginname"
<el-input :disabled="!!formData.matchedUserId" maxlength="50" placeholder="留空自动生成,也可手动填写" v-model="formData.loginname"
type="text"></el-input>
</el-form-item>
<el-form-item label="性&emsp;&emsp;别" prop="sex">
<el-radio-group v-model="formData.sex">
<el-radio-group v-model="formData.sex" :disabled="!!formData.matchedUserId && !!selectedExternalUser && !!selectedExternalUser.sex">
<el-radio :label="'男'" border>男</el-radio>
<el-radio :label="'女'" border>女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="身份证号" prop="idcard">
<el-input maxlength="18" placeholder="请填写身份证号"
v-model="formData.idcard" @input="updateExternalBirthday"></el-input>
</el-form-item>
<el-form-item label="出生年月" prop="birthday">
<el-date-picker
placeholder="选择出生年月"
style="width: 100%"
type="date"
v-model="formData.birthday"
value-format="yyyy-MM-dd">
</el-date-picker>
<el-date-picker style="width: 100%" type="date" value-format="yyyy-MM-dd"
placeholder="从身份证号读取,也可手动选择" v-model="formData.birthday"></el-date-picker>
</el-form-item>
<el-form-item label="电&emsp;&emsp;话" prop="mobile">
<el-input maxlength="11" placeholder="请填写电话" type="text"
v-model="formData.mobile"></el-input>
</el-form-item>
<el-form-item prop="unitId" label="所在单位">
<el-select clearable filterable placeholder="请选择所在单位" style="width: 100%"
disabled v-model="formData.unitId">
<el-option
v-for="item in unitOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
<el-form-item prop="unitname" label="所在单位">
<el-select v-if="formData.matchedUserId" filterable style="width: 100%"
placeholder="请选择报名使用的单位" v-model="formData.unitId" @change="changeExternalUnit">
<el-option v-for="item in unitOptions" :key="item.id" :label="item.name"
:value="item.id"></el-option>
</el-select>
<el-autocomplete v-else style="width: 100%" maxlength="100"
placeholder="搜索选择单位,或直接输入其他单位"
:fetch-suggestions="searchExternalUnit" v-model="formData.unitname"
@input="changeExternalUnitInput" @select="selectExternalUnit"></el-autocomplete>
</el-form-item>
<el-form-item label="身体条件" prop="fitForCompetition">
<el-radio-group v-model="formData.fitForCompetition">
<el-radio :label="true" border>适合参赛</el-radio>
<el-radio :label="false" border>不适合参赛</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="doAddUser">确 定</el-button>
<el-button type="primary" :loading="formLoading" :disabled="formData.fitForCompetition !== true"
@click="doAddUser(false)">确 定</el-button>
</span>
</el-dialog>
@@ -966,35 +966,60 @@ layout("/layouts/platform.html"){
:visible.sync="dialogVisible2"
width="40%"
>
<el-form :model="formData" ref="form" :rules="formRules" label-width="100px">
<el-form :model="formData" ref="singleExternalForm" :rules="lsryRules" label-width="100px">
<el-form-item prop="username" label="姓&emsp;&emsp;名">
<el-input maxlength="50" placeholder="请填写姓名" v-model="formData.username"
type="text"></el-input>
<el-autocomplete style="width: 100%" maxlength="50" :trigger-on-focus="false"
placeholder="输入姓名或工号搜索,未匹配时直接填写姓名"
:fetch-suggestions="searchExternalUser" v-model="formData.username"
@input="changeExternalName" @select="selectExternalUser">
<template slot-scope="{ item }">{{item.username}}({{item.loginname}})</template>
</el-autocomplete>
</el-form-item>
<el-form-item prop="loginname" label="工&emsp;&emsp;号">
<el-input maxlength="50" placeholder="请填写工号" v-model="formData.loginname"
<el-input :disabled="!!formData.matchedUserId" maxlength="50" placeholder="留空自动生成,也可手动填写" v-model="formData.loginname"
type="text"></el-input>
</el-form-item>
<el-form-item label="性&emsp;&emsp;别" prop="sex">
<el-radio-group v-model="formData.sex" :disabled="!!formData.matchedUserId && !!selectedExternalUser && !!selectedExternalUser.sex">
<el-radio :label="'男'" border>男</el-radio>
<el-radio :label="'女'" border>女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="身份证号" prop="idcard">
<el-input maxlength="18" placeholder="请填写身份证号"
v-model="formData.idcard" @input="updateExternalBirthday"></el-input>
</el-form-item>
<el-form-item label="出生年月" prop="birthday">
<el-date-picker style="width: 100%" type="date" value-format="yyyy-MM-dd"
placeholder="从身份证号读取,也可手动选择" v-model="formData.birthday"></el-date-picker>
</el-form-item>
<el-form-item prop="mobile" label="电&emsp;&emsp;话">
<el-input maxlength="50" placeholder="请填写电话" v-model="formData.mobile"
<el-input maxlength="11" placeholder="请填写电话" v-model="formData.mobile"
type="text"></el-input>
</el-form-item>
<el-form-item prop="unitId" label="所在单位">
<el-select clearable filterable placeholder="请选择所在单位" style="width: 100%"
v-model="formData.unitId">
<el-option
v-for="item in unitOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
<el-form-item prop="unitname" label="所在单位">
<el-select v-if="formData.matchedUserId" filterable style="width: 100%"
placeholder="请选择报名使用的单位" v-model="formData.unitId" @change="changeExternalUnit">
<el-option v-for="item in unitOptions" :key="item.id" :label="item.name"
:value="item.id"></el-option>
</el-select>
<el-autocomplete v-else style="width: 100%" maxlength="100"
placeholder="搜索选择单位,或直接输入其他单位"
:fetch-suggestions="searchExternalUnit" v-model="formData.unitname"
@input="changeExternalUnitInput" @select="selectExternalUnit"></el-autocomplete>
</el-form-item>
<el-form-item label="身体条件" prop="fitForCompetition">
<el-radio-group v-model="formData.fitForCompetition">
<el-radio :label="true" border>适合参赛</el-radio>
<el-radio :label="false" border>不适合参赛</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible2 = false">取 消</el-button>
<el-button type="primary" @click="doAddUser2">确 定</el-button>
<el-button type="primary" :loading="formLoading" :disabled="formData.fitForCompetition !== true"
@click="doAddUser(true)">确 定</el-button>
</span>
</el-dialog>
@@ -1058,6 +1083,10 @@ layout("/layouts/platform.html"){
},
activeName: '',
userOptions: [],
selectedExternalUser: null,
selectedExternalUnit: null,
externalSearchLoading: false,
externalSearchQuery: '',
applyUsers: [],
applyValue: [],
applyData: [],
@@ -1109,13 +1138,30 @@ layout("/layouts/platform.html"){
// userId: [{required: true, message: '必填', trigger: ['blur', 'change']}],
},
lsryRules: {
loginname: [{required: true, message: '必填', trigger: ['blur', 'change']}],
username: [{required: true, message: '请填写姓名', trigger: 'blur'}],
loginname: [{validator: (rule, value, callback) => {
if (this.formData.matchedUserId && !value) {
callback(new Error('所选系统人员没有工号'))
return
}
callback()
}, trigger: ['blur', 'change']}],
sex: [{required: true, message: '必填', trigger: ['blur', 'change']}],
idcard: [{validator: (rule, value, callback) => {
const card = (value || '').toUpperCase()
const date = card.substring(6, 14)
if (!/^[0-9]{17}[0-9X]$/.test(card) || !moment(date, 'YYYYMMDD', true).isValid()) {
callback(new Error('请填写正确的18位身份证号'))
return
}
callback()
}, trigger: ['blur', 'change']}],
birthday: [{required: true, message: '请选择出生年月', trigger: 'change'}],
mobile: [
{required: true, message: '手机号码', trigger: ['blur', 'change']},
{pattern: /^1[3456789]\d{9}$/, message: '请输入正确的手机号码'}],
unitId: [{required: true, message: '必填', trigger: ['blur', 'change']}],
userId: [{required: true, message: '必填', trigger: ['blur', 'change']}],
unitname: [{required: true, message: '请选择或填写单位', trigger: ['blur', 'change']}],
fitForCompetition: [{required: true, message: '请选择身体条件', trigger: 'change'}],
},
submitLoading: false,
selection: [],
@@ -1141,54 +1187,6 @@ layout("/layouts/platform.html"){
'delete_info': httpVueLoader('/components/activity/school/DeleteInfo.vue'),
},
methods: {
//查找今年临时人员
async getGhLsRy() {
const resp = await $.get(loc() + "/getGhLsRy")
return resp.data
},
async addUserChange(val) {
const user = this.lsUserList.find(l => l.id === val)
let loginname = "GHLS" + moment().format('YYYY') + "001"
//如果数据库找不到此人
if (!user) {
const lsryUser = await this.getGhLsRy()
//如果有临时人员
if (lsryUser) {
let str = lsryUser.loginname;
let prefix = "GHLS";
// 找到"GHLS"在字符串中首次出现的位置
let index = str.indexOf(prefix);
// 如果"GHLS"确实存在于字符串中,并且不是在字符串开头
if (index !== -1 && index + prefix.length < str.length) {
loginname = prefix + (Number(str.slice(index + prefix.length)) + 1);
}
}
}
this.$set(this.formData, 'userId', user ? user.id : val)
this.$set(this.formData, 'loginname', user ? user.loginname : loginname)
this.$set(this.formData, 'username', user ? user.username : val)
this.$set(this.formData, 'unitId', user ? user.unitid : "${@shiro.getPrincipalProperty('unit').getId()}")
this.$set(this.formData, 'sex', user ? user.sex : null)
this.$set(this.formData, 'birthday', user ? user.birthday : null)
this.$set(this.formData, 'mobile', user ? user.mobile : null)
this.$set(this.formData, 'status', 2)
},
async querySearchAsync(queryString) {
const applyDataIdList = this.applyData.map(m => m.id)
const appliedDataUserIdList = this.appliedData.map(m => m.userId)
const ids = applyDataIdList.concat(appliedDataUserIdList)
const {code, data} = await $.get(loc() + "/getSysUser", {
queryString: queryString,
})
if (code === 0) {
this.lsUserList = data
} else {
this.lsUserList = []
}
},
setLeader2() {
if (!this.selection.length) {
this.$notify({
@@ -1339,149 +1337,165 @@ layout("/layouts/platform.html"){
handleSelectionChange(val) {
this.selection = val;
},
async changeLeader() {
const userData = await this.getUserData(this.LeaderData.userId);
this.$set(this.LeaderData, 'userId', this.LeaderData.userId)
this.$set(this.LeaderData, 'loginname', userData.loginname)
this.$set(this.LeaderData, 'username', userData.username)
this.$set(this.LeaderData, 'unitId', userData.unitid)
this.$set(this.LeaderData, 'sex', userData.sex)
this.$set(this.LeaderData, 'mobile', userData.mobile)
this.$set(this.LeaderData, 'unionLeader', true)
this.$set(this.LeaderData, 'unionId', userData.unionid)
this.$set(this.LeaderData, 'identity', ['3'])
this.$set(this.LeaderData, 'status', 2)
this.$set(this.LeaderData, 'awardsMode', 2)
this.$set(this.LeaderData, 'activityId', this.activityData2.id)
this.$set(this.LeaderData, 'divisionLevelLeadership', false)
this.$set(this.LeaderData, 'unionCoach', false)
changeLeader(value) {
if (!value) {
this.LeaderData = {}
return
}
this.getUserData(value).then((userData) => {
if (this.LeaderData.userId !== value) return
this.$set(this.LeaderData, 'userId', this.LeaderData.userId)
this.$set(this.LeaderData, 'loginname', userData.loginname)
this.$set(this.LeaderData, 'username', userData.username)
this.$set(this.LeaderData, 'unitId', userData.unitid)
this.$set(this.LeaderData, 'sex', userData.sex)
this.$set(this.LeaderData, 'mobile', userData.mobile)
this.$set(this.LeaderData, 'unionLeader', true)
this.$set(this.LeaderData, 'unionId', userData.unionid)
this.$set(this.LeaderData, 'identity', ['3'])
this.$set(this.LeaderData, 'status', 2)
this.$set(this.LeaderData, 'awardsMode', 2)
this.$set(this.LeaderData, 'activityId', this.activityData2.id)
this.$set(this.LeaderData, 'divisionLevelLeadership', false)
this.$set(this.LeaderData, 'unionCoach', false)
})
},
async changeCoach() {
const userData = await this.getUserData(this.CoachData.userId);
this.$set(this.CoachData, 'userId', this.CoachData.userId)
this.$set(this.CoachData, 'loginname', userData.loginname)
this.$set(this.CoachData, 'username', userData.username)
this.$set(this.CoachData, 'unitId', userData.unitid)
this.$set(this.CoachData, 'sex', userData.sex)
this.$set(this.CoachData, 'mobile', userData.mobile)
this.$set(this.CoachData, 'unionCoach', true)
this.$set(this.CoachData, 'unionId', userData.unionid)
this.$set(this.CoachData, 'identity', ['2'])
this.$set(this.CoachData, 'status', 2)
this.$set(this.CoachData, 'awardsMode', 2)
this.$set(this.CoachData, 'activityId', this.activityData2.id)
this.$set(this.CoachData, 'divisionLevelLeadership', false)
this.$set(this.CoachData, 'unionLeader', false)
changeCoach(value) {
if (!value) {
this.CoachData = {}
return
}
this.getUserData(value).then((userData) => {
if (this.CoachData.userId !== value) return
this.$set(this.CoachData, 'userId', this.CoachData.userId)
this.$set(this.CoachData, 'loginname', userData.loginname)
this.$set(this.CoachData, 'username', userData.username)
this.$set(this.CoachData, 'unitId', userData.unitid)
this.$set(this.CoachData, 'sex', userData.sex)
this.$set(this.CoachData, 'mobile', userData.mobile)
this.$set(this.CoachData, 'unionCoach', true)
this.$set(this.CoachData, 'unionId', userData.unionid)
this.$set(this.CoachData, 'identity', ['2'])
this.$set(this.CoachData, 'status', 2)
this.$set(this.CoachData, 'awardsMode', 2)
this.$set(this.CoachData, 'activityId', this.activityData2.id)
this.$set(this.CoachData, 'divisionLevelLeadership', false)
this.$set(this.CoachData, 'unionLeader', false)
})
},
async changeStaff() {
const userData = await this.getUserData(this.staffData.userId);
this.$set(this.staffData, 'userId', this.staffData.userId)
this.$set(this.staffData, 'loginname', userData.loginname)
this.$set(this.staffData, 'username', userData.username)
this.$set(this.staffData, 'unitId', userData.unitid)
this.$set(this.staffData, 'sex', userData.sex)
this.$set(this.staffData, 'mobile', userData.mobile)
this.$set(this.staffData, 'unionStaff', true)
this.$set(this.staffData, 'identity', ['7'])
this.$set(this.staffData, 'unionId', userData.unionid)
this.$set(this.staffData, 'status', 2)
this.$set(this.staffData, 'awardsMode', 2)
this.$set(this.staffData, 'activityId', this.activityData2.id)
changeStaff(value) {
if (!value) {
this.staffData = {}
return
}
this.getUserData(value).then((userData) => {
if (this.staffData.userId !== value) return
this.$set(this.staffData, 'userId', this.staffData.userId)
this.$set(this.staffData, 'loginname', userData.loginname)
this.$set(this.staffData, 'username', userData.username)
this.$set(this.staffData, 'unitId', userData.unitid)
this.$set(this.staffData, 'sex', userData.sex)
this.$set(this.staffData, 'mobile', userData.mobile)
this.$set(this.staffData, 'unionStaff', true)
this.$set(this.staffData, 'identity', ['7'])
this.$set(this.staffData, 'unionId', userData.unionid)
this.$set(this.staffData, 'status', 2)
this.$set(this.staffData, 'awardsMode', 2)
this.$set(this.staffData, 'activityId', this.activityData2.id)
})
},
async changeHead() {
const userData = await this.getUserData(this.headData.userId);
this.$set(this.headData, 'userId', this.headData.userId)
this.$set(this.headData, 'loginname', userData.loginname)
this.$set(this.headData, 'username', userData.username)
this.$set(this.headData, 'unitId', userData.unitid)
this.$set(this.headData, 'sex', userData.sex)
this.$set(this.headData, 'mobile', userData.mobile)
this.$set(this.headData, 'unionCoach', false)
this.$set(this.headData, 'unionHead', true)
this.$set(this.headData, 'unionId', userData.unionid)
this.$set(this.headData, 'identity', ['6'])
this.$set(this.headData, 'status', 2)
this.$set(this.headData, 'awardsMode', 2)
this.$set(this.headData, 'activityId', this.activityData2.id)
this.$set(this.headData, 'divisionLevelLeadership', false)
this.$set(this.headData, 'unionLeader', false)
changeHead(value) {
if (!value) {
this.headData = {}
return
}
this.getUserData(value).then((userData) => {
if (this.headData.userId !== value) return
this.$set(this.headData, 'userId', this.headData.userId)
this.$set(this.headData, 'loginname', userData.loginname)
this.$set(this.headData, 'username', userData.username)
this.$set(this.headData, 'unitId', userData.unitid)
this.$set(this.headData, 'sex', userData.sex)
this.$set(this.headData, 'mobile', userData.mobile)
this.$set(this.headData, 'unionCoach', false)
this.$set(this.headData, 'unionHead', true)
this.$set(this.headData, 'unionId', userData.unionid)
this.$set(this.headData, 'identity', ['6'])
this.$set(this.headData, 'status', 2)
this.$set(this.headData, 'awardsMode', 2)
this.$set(this.headData, 'activityId', this.activityData2.id)
this.$set(this.headData, 'divisionLevelLeadership', false)
this.$set(this.headData, 'unionLeader', false)
})
},
async doLeaderCoach() {
this.$refs["form"].validate(async (valid) => {
if (valid) {
let loading = this.$loading({
lock: true,
text: '正在提交...',
spinner: 'el-icon-loading',
background: COVER_LAYER_COLOR
});
const resp = await $.post(loc() + "/doLeaderCoachHead",
{
LeaderData: JSON.stringify(this.LeaderData),
CoachData: JSON.stringify(this.CoachData),
headData: JSON.stringify(this.headData),
staffData: JSON.stringify(this.staffData)
}
)
closeLoadingFunc(loading, resp, async () => {
this.activityData2.CoachCount = 1
this.activityData2.LeaderCount = 1
await this.getUnionCoachLeaderHead()
this.dialogLeaderCoach = false;
}, (data, msg) => {
this.$message.error(msg);
doLeaderCoach() {
this.$refs["leaderCoachForm"].validate((valid) => {
if (!valid) return
this.formLoading = true
$.post(loc() + "/doLeaderCoachHead", {
activityId: this.activityData2.id,
LeaderData: JSON.stringify(this.LeaderData),
CoachData: JSON.stringify(this.CoachData),
headData: JSON.stringify(this.headData),
staffData: JSON.stringify(this.staffData)
}).then((resp) => {
if (resp.code !== 0) {
this.$message.error(resp.msg)
return
}
return this.getUnionCoachLeaderHead().then((data) => {
if (!data) return
this.$set(this.activityData2, 'CoachCount', data.unionCoach ? 1 : 0)
this.$set(this.activityData2, 'LeaderCount', data.unionLeader ? 1 : 0)
this.dialogLeaderCoach = false
})
}
}).fail(() => {
this.$message.error('保存失败')
}).always(() => {
this.formLoading = false
})
})
},
async getUnionCoachLeaderHead() {
getUnionCoachLeaderHead() {
this.LeaderData = {}
this.CoachData = {}
this.headData = {}
this.staffData = {}
const resp = await $.get(loc() + "/getUnionCoachLeaderHead", {activityId: this.activityData2.id})
const {unionCoach, unionLeader, unionHead, unionStaff, userOptions} = resp.data
if (unionCoach) {
this.CoachData = unionCoach
}
if (unionLeader) {
this.LeaderData = unionLeader
}
if (unionHead) {
this.headData = unionHead
}
if (unionStaff) {
this.staffData = unionStaff
}
this.userOptions = userOptions
return $.post(loc() + "/getUnionCoachLeaderHead", {activityId: this.activityData2.id})
.then((resp) => {
if (resp.code !== 0) {
this.$message.error(resp.msg)
return null
}
const {unionCoach, unionLeader, unionHead, unionStaff, userOptions} = resp.data
if (unionCoach) this.CoachData = unionCoach
if (unionLeader) this.LeaderData = unionLeader
if (unionHead) this.headData = unionHead
if (unionStaff) this.staffData = unionStaff
this.userOptions = userOptions
return resp.data
})
},
async openLeaderCoach() {
if (this.activityData2.CoachCount > 0 || this.activityData2.LeaderCount > 0) {
await this.getUnionCoachLeaderHead()
this.dialogLeaderCoach = true
} else {
this.LeaderData = {}
this.CoachData = {}
this.dialogLeaderCoach = true
}
openLeaderCoach() {
this.getUnionCoachLeaderHead().then((data) => {
if (data) this.dialogLeaderCoach = true
})
},
async userLeaderCoach(query) {
if (query) {
this.userLoading = true
const resp = await $.get(loc() + "/getUserUnion", {query})
this.userOptions = resp
this.userLoading = false
}
userLeaderCoach(query) {
if (!query) return
this.userLoading = true
$.post(loc() + "/getUserUnion", {query})
.then((resp) => {
this.userOptions = resp
}).always(() => {
this.userLoading = false
})
},
joinMemberFamily() {
if (!this.selection.length) {
@@ -1523,30 +1537,33 @@ layout("/layouts/platform.html"){
this.openSingleApply(row)
}*/
},
async openApply(item) {
openApply(item) {
this.$set(this.pageForm, "isMenWomen", 1)
this.activityData2 = item
this.pageForm.activityId = item.id
if (item.applyType == 2) {
await this.getUnionCoachLeaderHead()
this.unionLeaderCoach = true
this.getUnionCoachLeaderHead().then((data) => {
if (!data) return
this.unionLeaderCoach = true
this.pageData()
this.changeActivit()
})
} else {
this.unionLeaderCoach = false
this.pageData()
this.changeActivit()
}
this.pageData()
await this.changeActivit()
},
async changeYear() {
const resp = await $.post(loc() + "/getActivits", {year: this.pageForm.year})
this.activitys = resp
},
async changeActivit() {
const resp = await $.post("/platform/activity/apply/getEvents", {activityId: this.pageForm.activityId})
this.events = resp
changeActivit() {
return $.post("/platform/activity/apply/getEvents", {activityId: this.pageForm.activityId})
.then((resp) => {
this.events = resp
})
},
handleTabClick(tab) {
@@ -1678,9 +1695,8 @@ layout("/layouts/platform.html"){
})
},
async getUserData(userId) {
const {data} = await $.get(loc() + "/getUserData", {userId})
return data
getUserData(userId) {
return $.post(loc() + "/getUserData", {userId}).then((res) => res.data)
},
async userChange() {
@@ -1732,74 +1748,200 @@ layout("/layouts/platform.html"){
this.leaderOptions = [];
}
},
doAddUser() {
this.$refs["form"].validate(async (valid) => {
if (valid) {
const data = {
isAddUser: true,
age: 0,
eventId: this.eventData.eventId,
activityId: this.activityData.id,
team: this.teams.find(x => x.id === this.formData.teamId).name,
unitname: this.unitOptions.find(x => x.id === this.formData.unitId).name,
...this.formData
}
if (this.formData.birthday) {
const birthDate = moment(this.formData.birthday);
const age = moment().diff(birthDate, 'years');
data.age = age
}
const unitId = "${@shiro.getPrincipalProperty('unit').getId()}"
const newUser = this.applyUsers.find(a => a.unitid === unitId &&
a.username === this.formData.username)
if (newUser) {
this.notifyWarning('该窗口只支持添加外援!')
return
}
this.appliedData.push(data)
this.sortApplyed()
this.flushApplyed(this.appliedData)
this.$notify.success({title: '成功', message: '已添加'});
this.dialogVisible = false
} else {
this.$notify.error({title: '错误', message: '存在未填写的的必填项'});
searchExternalUser(query, callback) {
// 姓名输入框既可搜索系统人员,也可直接填写新人员姓名。
this.externalSearchQuery = query
if (!query || !query.trim()) {
this.externalSearchLoading = false
callback([])
return
}
this.externalSearchLoading = true
$.post(loc() + "/getSysUser", {queryString: query}).then((res) => {
if (this.externalSearchQuery !== query) {
callback([])
return
}
const users = res.code === 0 ? (res.data || []) : []
callback(users.map(user => ({...user, value: user.username})))
}).fail(() => {
callback([])
}).always(() => {
if (this.externalSearchQuery === query) this.externalSearchLoading = false
})
},
doAddUser2() {
this.$refs["form"].validate(async (valid) => {
if (valid) {
const data = {
eventId: this.eventData.eventId,
activityId: this.activityData.id,
unitname: this.unitOptions.find(x => x.id === this.formData.unitId).name,
...this.formData
}
this.appliedData3.push(data)
this.sortApplyed()
this.flushApplyed(this.appliedData)
this.$notify.success({title: '成功', message: '已添加'});
this.dialogVisible2 = false
} else {
this.$notify.error({title: '错误', message: '存在未填写的的必填项'});
changeExternalName(value) {
if (!this.selectedExternalUser || value === this.selectedExternalUser.username) return
// 修改已选姓名即退出匹配,清除旧人的资料,避免作为新人员误提交。
this.selectedExternalUser = null
this.$set(this.formData, 'matchedUserId', '')
const fields = ['loginname', 'sex', 'idcard', 'birthday', 'mobile', 'unitname']
fields.forEach(key => {
this.$set(this.formData, key, '')
})
this.$set(this.formData, 'unitId', "${@shiro.getPrincipalProperty('unit').getId()}")
this.selectedExternalUnit = null
},
selectExternalUser(user) {
// 选中候选人后复用其账号,系统字段为空的部分可在下方补填。
this.selectedExternalUser = user
this.$set(this.formData, 'matchedUserId', user.id)
const fields = ['username', 'loginname', 'sex', 'idcard', 'birthday', 'mobile']
fields.forEach(key => {
this.$set(this.formData, key, user[key] || '')
})
const card = (user.idcard || '').toUpperCase()
const date = card.substring(6, 14)
if (!user.birthday && /^[0-9]{17}[0-9X]$/.test(card) && moment(date, 'YYYYMMDD', true).isValid()) {
this.$set(this.formData, 'birthday', moment(date, 'YYYYMMDD').format('YYYY-MM-DD'))
}
const unit = this.unitOptions.find(item => item.id === user.unitid)
this.$set(this.formData, 'unitId', unit ? unit.id : '')
this.$set(this.formData, 'unitname', unit ? unit.name : '')
},
changeExternalUnit(id) {
const unit = this.unitOptions.find(item => item.id === id)
this.$set(this.formData, 'unitname', unit ? unit.name : '')
},
searchExternalUnit(query, callback) {
const units = this.unitOptions.filter(item => !query || item.name.indexOf(query) > -1)
callback(units.slice(0, 30).map(item => ({...item, value: item.name})))
},
changeExternalUnitInput(value) {
// 手动改写单位名称后解除已选单位 ID,报名归属仍沿用操作人的单位。
if (this.selectedExternalUnit && value !== this.selectedExternalUnit.name) {
this.selectedExternalUnit = null
this.$set(this.formData, 'unitId', "${@shiro.getPrincipalProperty('unit').getId()}")
}
},
selectExternalUnit(unit) {
this.selectedExternalUnit = unit
this.$set(this.formData, 'unitId', unit.id)
this.$set(this.formData, 'unitname', unit.name)
},
updateExternalBirthday(value) {
// 身份证号变化时重新计算出生年月,避免沿用系统中可能错误的日期。
const card = (value || '').toUpperCase()
const date = card.substring(6, 14)
const birthday = /^[0-9]{17}[0-9X]$/.test(card) && moment(date, 'YYYYMMDD', true).isValid()
? moment(date, 'YYYYMMDD').format('YYYY-MM-DD') : ''
this.$set(this.formData, 'birthday', birthday)
},
addExternalToApply(user, single) {
const data = {
...this.formData,
userId: user.id,
loginname: user.loginname,
username: user.username,
sex: this.formData.sex,
idcard: this.formData.idcard,
idCard: this.formData.idcard,
birthday: this.formData.birthday,
mobile: this.formData.mobile,
unitId: this.formData.unitId,
unitname: this.formData.unitname.trim(),
age: this.formData.birthday ? moment().diff(moment(this.formData.birthday), 'years') : null,
eventId: this.eventData.eventId,
activityId: this.activityData.id,
team: single ? null : this.teams.find(item => item.id === this.formData.teamId).name,
isAddUser: true
}
if (single) {
this.appliedData3.push(data)
} else {
this.appliedData.push(data)
}
// 系统人员可能仍在左侧可报名单中,移除该选项以免同一项目重复加入。
this.applyData = this.applyData.filter(item => item.id !== user.id)
this.applyValue = this.applyValue.filter(id => id !== user.id)
this.rightChecked = this.rightChecked.filter(id => id !== user.id)
this.sortApplyed()
this.flushApplyed(this.appliedData)
this.notifySuccess('已添加报名人员')
this.dialogVisible = false
this.dialogVisible2 = false
},
doAddUser(single) {
// 匹配人员直接加入报名;未匹配人员先创建账号,再加入本次报名。
if (this.formData.fitForCompetition !== true) return
this.$refs[single ? 'singleExternalForm' : 'form'].validate((valid) => {
if (!valid) {
this.$notify.error({title: '错误', message: '存在未填写或不正确的必填项'})
return
}
if (!this.formData.unitname || !this.formData.unitname.trim()) {
this.notifyError('请选择或填写单位')
return
}
if (this.appliedData.concat(this.appliedData3).some(item => item.userId === this.formData.matchedUserId)) {
this.notifyError('该人员已在当前项目报名列表中')
return
}
if (this.formData.matchedUserId) {
if (this.selectedExternalUser && this.selectedExternalUser.id === this.formData.matchedUserId) {
this.addExternalToApply(this.selectedExternalUser, single)
}
return
}
this.formLoading = true
$.post(loc() + "/addExternalUser", {
username: this.formData.username,
loginname: this.formData.loginname,
sex: this.formData.sex,
idcard: this.formData.idcard,
mobile: this.formData.mobile,
fitForCompetition: this.formData.fitForCompetition
}).then((res) => {
if (res.code !== 0) {
this.notifyError(res.msg || '外援创建失败')
return
}
this.addExternalToApply(res.data, single)
}).fail(() => {
this.notifyError('外援创建失败,请重试')
}).always(() => {
this.formLoading = false
})
})
},
openAddUser() {
this.formData = {teamId: this.team, identity: ['1']}
this.dialogVisible = true
if (this.$refs['form']) {
this.$refs['form'].resetFields()
this.formData = {
teamId: this.team,
identity: ['1'],
username: '',
loginname: '',
sex: '',
idcard: '',
birthday: '',
mobile: '',
unitId: "${@shiro.getPrincipalProperty('unit').getId()}",
unitname: '',
matchedUserId: '',
fitForCompetition: null
}
this.selectedExternalUser = null
this.selectedExternalUnit = null
this.externalSearchQuery = ''
this.dialogVisible = true
},
openAddUserD() {
this.formData = {identity: ['1']}
this.dialogVisible2 = true
if (this.$refs['form']) {
this.$refs['form'].resetFields()
this.formData = {
identity: ['1'],
username: '',
loginname: '',
sex: '',
idcard: '',
birthday: '',
mobile: '',
unitId: "${@shiro.getPrincipalProperty('unit').getId()}",
unitname: '',
matchedUserId: '',
fitForCompetition: null
}
this.selectedExternalUser = null
this.selectedExternalUnit = null
this.externalSearchQuery = ''
this.dialogVisible2 = true
},
async doApplied(flag1) {
@@ -121,8 +121,11 @@ layout("/layouts/platform.html"){
</template>
<template v-else-if="column.prop=='startTime'" scope="{row}">
{{moment(row.startTime).format('YYYY-MM-DD')}} -
{{moment(row.endTime).format('YYYY-MM-DD')}}
<span v-if="row.startTime && row.endTime">
{{moment(row.startTime).format('YYYY-MM-DD')}} -
{{moment(row.endTime).format('YYYY-MM-DD')}}
</span>
<span v-else>未设置</span>
</template>
<template v-else-if="column.prop=='state'"
@@ -356,7 +359,7 @@ layout("/layouts/platform.html"){
if ("${@shiro.hasRole('H02')}" === "true") {
this.is_club = true
}
if ("${@shiro.hasRole('H04')}" === "true") {
if ("${@shiro.hasRole('H04')}" === "true" || "${@shiro.hasRole('SportsActivityUnionAdmin')}" === "true") {
this.is_union = true
}
if ("${@shiro.hasRole('sysadmin')}" === "true") {
@@ -84,7 +84,7 @@ layout("/layouts/platform.html"){
</div>
<!--#if(!@shiro.hasRole('H04')){#-->
<!--#if(!@shiro.hasRole('H04')&&!@shiro.hasRole('SportsActivityUnionAdmin')){#-->
<div class="search-item">
<div class="search-item-label">报名状态</div>
<div class="search-item-option">
@@ -591,6 +591,7 @@ layout("/layouts/platform.html"){
{prop: 'username', label: '姓名'},
{prop: 'loginname', label: '教工号'},
{prop: 'sex', label: '性别'},
{prop: 'birthday', label: '出生年月'},
/* {prop: 'divisionLevelLeadership', label: '是否处级领导', sortable: true},*/
{prop: 'unitname', label: '单位', sortable: true},
{prop: 'mobile', label: '电话'},
@@ -110,6 +110,9 @@ layout("/layouts/platform.html"){
{prop: 'loginname', label: '申请人工号'},
{prop: 'username', label: '申请人姓名'},
{prop: 'mobile', label: '申请人电话'},
{prop: 'sex', label: '性别'},
{prop: 'birthday', label: '出生年月'},
{prop: 'unitname', label: '单位'},
{prop: 'identity', label: '申请角色'},
{prop: 'allName', label: '活动项目'},
{prop: 'startTime', label: '活动日期'},
@@ -17,8 +17,8 @@ layout("/layouts/platform.html"){
<div class="search-item-option">
<el-date-picker
style="width: 100%"
placeholder="选择年度"
type="year"
placeholder="全部年度"
type="year" clearable @change="handleYearChange"
v-model="pageForm.year"
value-format="yyyy">
</el-date-picker>
@@ -27,13 +27,13 @@ layout("/layouts/platform.html"){
<div class="search-item">
<div class="search-item-label">项目名称:</div>
<div class="search-item-option">
<el-select v-model="pageForm.projectId" placeholder="请选择项目名称" filterable clearable
<el-select v-model="pageForm.projectId" placeholder="请选择项目名称" filterable clearable :loading="projectLoading"
style="width: 100%" @change="doSearch">
<el-option
v-for="item in projectList"
:key="item.code"
:key="item.id"
:label="item.name"
:value="item.code">
:value="item.id">
</el-option>
</el-select>
</div>
@@ -113,6 +113,7 @@ layout("/layouts/platform.html"){
],
buyClothesUserData: [],
projectList: [],
projectLoading: false,
unionList: [],
}
},
@@ -128,15 +129,43 @@ layout("/layouts/platform.html"){
this.$refs['guava'].view()
}
},
//获取所有买服装的项目
async getBuyClothesProject() {
const {data} = await $.post('/platform/clothing/register/getBuyClothesProject')
return data
// 年度筛选与活动管理页一致,按活动报名开始时间所属年度获取活动。
getBuyClothesProject(year) {
this.$set(this, 'projectLoading', true)
return $.post('/platform/clothing/register/getBuyClothesProject', {year: year})
.then((resp) => {
if (resp.code === 0) return resp.data || []
this.notifyWarning(resp.msg)
return []
}, () => {
this.notifyWarning('活动列表加载失败')
return []
})
.always(() => {
this.$set(this, 'projectLoading', false)
})
},
// 年度变化后清空不属于新年度的项目选择,再按当前条件查询台账。
handleYearChange() {
const year = this.pageForm.year
this.$set(this.pageForm, 'projectId', null)
this.$set(this, 'projectList', [])
this.getBuyClothesProject(year).then((projects) => {
if (this.pageForm.year !== year) return
this.$set(this, 'projectList', projects)
this.doSearch()
})
},
},
async created() {
this.projectList = await this.getBuyClothesProject()
this.unionList = await getUnion()
created() {
const year = this.pageForm.year
this.getBuyClothesProject(year).then((projects) => {
if (this.pageForm.year !== year) return
this.$set(this, 'projectList', projects)
})
getUnion().then((unions) => {
this.$set(this, 'unionList', unions)
})
this.pageData()
}
})
@@ -17,8 +17,8 @@ layout("/layouts/platform.html"){
<div class="search-item-option">
<el-date-picker
style="width: 100%"
placeholder="选择年度"
type="year"
placeholder="全部年度"
type="year" clearable @change="handleYearChange"
v-model="pageForm.year"
value-format="yyyy">
</el-date-picker>
@@ -27,13 +27,13 @@ layout("/layouts/platform.html"){
<div class="search-item">
<div class="search-item-label">项目名称:</div>
<div class="search-item-option">
<el-select v-model="pageForm.projectId" placeholder="请选择项目名称" filterable clearable
<el-select v-model="pageForm.projectId" placeholder="请选择项目名称" filterable clearable :loading="projectLoading"
style="width: 100%" @change="doSearch">
<el-option
v-for="item in projectList"
:key="item.code"
:key="item.id"
:label="item.name"
:value="item.code">
:value="item.id">
</el-option>
</el-select>
</div>
@@ -111,6 +111,7 @@ layout("/layouts/platform.html"){
],
buyClothesUserData: [],
projectList: [],
projectLoading: false,
note: '',
clickRow: {},
}
@@ -177,10 +178,32 @@ layout("/layouts/platform.html"){
}
}
},
//获取所有买服装的项目
async getBuyClothesProject() {
const {data} = await $.post('/platform/clothing/register/getBuyClothesProject')
return data
// 年度筛选与活动管理页一致,按活动报名开始时间所属年度获取活动。
getBuyClothesProject(year) {
this.$set(this, 'projectLoading', true)
return $.post('/platform/clothing/register/getBuyClothesProject', {year: year})
.then((resp) => {
if (resp.code === 0) return resp.data || []
this.notifyWarning(resp.msg)
return []
}, () => {
this.notifyWarning('活动列表加载失败')
return []
})
.always(() => {
this.$set(this, 'projectLoading', false)
})
},
// 年度变化后清空不属于新年度的项目选择,再按当前条件查询登记记录。
handleYearChange() {
const year = this.pageForm.year
this.$set(this.pageForm, 'projectId', null)
this.$set(this, 'projectList', [])
this.getBuyClothesProject(year).then((projects) => {
if (this.pageForm.year !== year) return
this.$set(this, 'projectList', projects)
this.doSearch()
})
},
doExport(row) {
const loading = this.$loading({
@@ -221,8 +244,12 @@ layout("/layouts/platform.html"){
}).catch((err) => {})
},
},
async created() {
this.projectList = await this.getBuyClothesProject()
created() {
const year = this.pageForm.year
this.getBuyClothesProject(year).then((projects) => {
if (this.pageForm.year !== year) return
this.$set(this, 'projectList', projects)
})
this.pageData()
}
})
@@ -27,8 +27,8 @@ layout("/layouts/platform.html"){
<div class="search-item-option">
<el-date-picker
style="width: 100%"
placeholder="选择年度"
type="year" disabled
placeholder="全部年度"
type="year" clearable @change="handleYearChange"
v-model="pageForm.year"
value-format="yyyy">
</el-date-picker>
@@ -37,19 +37,24 @@ layout("/layouts/platform.html"){
<div class="search-item">
<div class="search-item-label">项目名称:</div>
<div class="search-item-option">
<el-select v-model="pageForm.projectId" placeholder="请选择项目名称" filterable
<el-select v-model="pageForm.projectId" placeholder="请选择项目名称" filterable :loading="projectLoading"
@change="doSearch" style="width: 100%">
<el-option
v-for="item in projectList"
:key="item.code"
:key="item.id"
:label="item.name"
:value="item.code">
:value="item.id">
</el-option>
</el-select>
</div>
</div>
<div class="search-item">
<div class="search-item-label">近两年内:</div>
<div class="search-item-label" style="white-space: nowrap;">
近两年内:
<el-tooltip content="按所选活动的报名年度,检查相邻年度活动中相同运动项目的购衣记录;补录往年活动也会查重" placement="top">
<i class="el-icon-question" style="cursor: help;"></i>
</el-tooltip>
</div>
<div class="search-item-option">
<el-radio-group @change="(val) => doSearch()" v-model="pageForm.isBuy">
<el-radio-button :label="true">已购买</el-radio-button>
@@ -112,22 +117,25 @@ layout("/layouts/platform.html"){
</template>
</vi-title2>
</div>
<el-table ref="multipleTable" @selection-change="handleSelectionChange" :data="userData" max-height="600"
<el-table ref="multipleTable" v-loading="tableLoading" @selection-change="handleSelectionChange" :data="userData" max-height="600"
:row-key="(val) => val.id">
<el-table-column :reserve-selection="true" type="selection" width="55"></el-table-column>
<el-table-column :reserve-selection="true" :selectable="canSelectUser" type="selection" width="55"></el-table-column>
<el-table-column label="姓名" prop="username" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="工号" prop="loginname" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="sex" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="单位" prop="unitname" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属工会" prop="unionname" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="联系方式" prop="mobile" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="pageForm.isBuy" label="购买来源" align="center" header-align="center">
<template slot-scope="scope">{{scope.row.canRemove ? '当前活动' : '两年内同类活动'}}</template>
</el-table-column>
</el-table>
<div style="display: flex; justify-content: space-between; margin-top: 6px">
<div> <!--#include("/layouts/pagination.html"){}#--></div>
<div>
<el-row justify="end" type="flex">
<el-button v-if="!pageForm.isBuy" @click="doHandler" type="primary">提交</el-button>
<el-button v-else @click="doHandler" type="danger">移除</el-button>
<el-button v-if="!pageForm.isBuy" @click="doHandler" :loading="formLoading" type="primary">提交</el-button>
<el-button v-else @click="doHandler" :loading="formLoading" type="danger">移除</el-button>
</el-row>
</div>
</div>
@@ -154,11 +162,12 @@ layout("/layouts/platform.html"){
data() {
return {
pageForm: {
year: moment().format('YYYY'),
year: null,
isBuy: false,
searchName: 'u.username'
},
projectList: [],
projectLoading: false,
userData: [],
userValue: [],
treeUnionList: [],
@@ -172,7 +181,11 @@ layout("/layouts/platform.html"){
},
methods: {
handleSelectionChange(val) {
this.userValue = val.length > 0 ? val.map(o => o.id) : []
this.$set(this, 'userValue', val.length > 0 ? val.map(o => o.id) : [])
},
// 其他同类活动的历史购买只能查看;移除操作仅处理当前活动的登记。
canSelectUser(row) {
return !this.pageForm.isBuy || row.canRemove
},
filterNode(value, data, node) {
if (!value || node.level === 1) return true;
@@ -187,7 +200,7 @@ layout("/layouts/platform.html"){
this.pageData()
}
},
async doHandler() {
doHandler() {
if(!this.pageForm.projectId) {
this.notifyWarning('请选择项目!')
return
@@ -196,50 +209,104 @@ layout("/layouts/platform.html"){
this.notifyWarning('请选择人员!')
return
}
const confirm = await this.$confirm('您选择了' + this.userValue.length + '人, 是否继续?', '提示', {
this.$confirm('您选择了' + this.userValue.length + '人, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
if (confirm === "confirm") {
const resp = await $.post('/platform/clothing/register/doHandler', {
}).then(() => {
this.$set(this, 'formLoading', true)
return $.post('/platform/clothing/register/doHandler', {
ids: JSON.stringify(this.userValue),
projectId: this.pageForm.projectId,
year: this.pageForm.year,
isBuy: this.pageForm.isBuy,
})
if(resp.code === 0) {
this.doSearch()
this.notifySuccess(resp.msg)
} else {
this.notifyWarning(resp.msg)
}
}
.then((resp) => {
if (resp.code === 0) {
this.doSearch()
this.notifySuccess(resp.msg)
} else {
this.notifyWarning(resp.msg)
}
}, () => {
this.notifyWarning('提交失败,请刷新名单后重试')
})
.always(() => {
this.$set(this, 'formLoading', false)
})
}).catch(() => {})
},
doSearch() {
this.$refs.multipleTable.clearSelection()
this.pageData()
},
//查询人员名单
async pageData() {
const resp = await $.post('/platform/clothing/register/pageData', this.pageForm)
if (resp.code === 0) {
this.userData = resp.data.list
this.pageForm.totalCount = resp.data.totalCount
} else {
this.$message.error(resp.msg)
}
// 查询与所选活动报名年度相差不超过一年、同类运动项目的购买状态。
pageData() {
const projectId = this.pageForm.projectId
const isBuy = this.pageForm.isBuy
this.$set(this, 'tableLoading', true)
return $.post('/platform/clothing/register/pageData', this.pageForm)
.then((resp) => {
if (this.pageForm.projectId !== projectId || this.pageForm.isBuy !== isBuy) return
if (resp.code === 0) {
this.$set(this, 'userData', resp.data.list)
this.$set(this.pageForm, 'totalCount', resp.data.totalCount)
} else {
this.$set(this, 'userData', [])
this.$set(this.pageForm, 'totalCount', 0)
this.$message.error(resp.msg)
}
}, () => {
if (this.pageForm.projectId !== projectId || this.pageForm.isBuy !== isBuy) return
this.$set(this, 'userData', [])
this.$set(this.pageForm, 'totalCount', 0)
this.$message.error('人员名单加载失败')
})
.always(() => {
this.$set(this, 'tableLoading', false)
})
},
//获取所有买服装的项目
async getBuyClothesProject() {
const {data} = await $.post('/platform/clothing/register/getBuyClothesProject')
return data
// 年度为空时获取全部已提交活动;指定年度时按活动报名开始时间筛选。
getBuyClothesProject(year) {
this.$set(this, 'projectLoading', true)
return $.post('/platform/clothing/register/getBuyClothesProject', {year: year})
.then((resp) => {
if (resp.code === 0) return resp.data || []
this.notifyWarning(resp.msg)
return []
}, () => {
this.notifyWarning('活动列表加载失败')
return []
})
.always(() => {
this.$set(this, 'projectLoading', false)
})
},
async unionTree() {
const resp = await $.post('/platform/clothing/register/getTreeUnionList')
if(resp.code === 0) {
this.sysadmin = resp.data.sysadmin
this.treeUnionList = [resp.data]
// 切换年度时清除原项目,避免用上一年度的活动ID继续查询或登记。
refreshProjects() {
const year = this.pageForm.year
this.$set(this.pageForm, 'projectId', null)
this.$set(this, 'projectList', [])
this.$set(this, 'userData', [])
this.$set(this, 'userValue', [])
return this.getBuyClothesProject(year).then((projects) => {
if (this.pageForm.year !== year) return
this.$set(this, 'projectList', projects)
if (projects.length > 0) {
this.$set(this.pageForm, 'projectId', projects[0].id)
}
})
},
handleYearChange() {
const year = this.pageForm.year
this.refreshProjects().then(() => {
if (this.pageForm.year === year) this.doSearch()
})
},
unionTree() {
return $.post('/platform/clothing/register/getTreeUnionList').then((resp) => {
if (resp.code !== 0) return
this.$set(this, 'sysadmin', resp.data.sysadmin)
this.$set(this, 'treeUnionList', [resp.data])
if (this.sysadmin) {
this.handleNodeClick({unioncode: ''}, {level: 1})
} else {
@@ -257,16 +324,15 @@ layout("/layouts/platform.html"){
level: 2, data: data
})
}
}
}, () => {
this.notifyWarning('工会列表加载失败')
}).always(() => {})
},
},
async created() {
this.projectList = await this.getBuyClothesProject()
if (this.projectList && this.projectList.length > 0) {
this.pageForm.projectId = this.projectList[0].code
}
await this.unionTree()
this.pageData()
created() {
this.refreshProjects().then(() => {
this.unionTree()
})
}
})
</script>
@@ -74,13 +74,23 @@ layout("/layouts/platform.html"){
color: white !important;
background-color: #314659 !important;
}
.dashboard-drill {
cursor: pointer;
}
.dashboard-drill:hover .card-value-num,
.dashboard-drill:hover .card-label {
color: #409EFF;
}
</style>
<div id="app" class="platform aid-dash-bord" v-cloak>
<guava ref="guava">
<template>
<el-row gutter="20" type="flex">
<el-col v-loading="loading.memberNumber">
<el-col class="dashboard-drill" v-loading="loading.memberNumber"
title="点击查看基金会员明细"
@click.native="openDetail('基金会员总数明细', 'member', {})">
<el-card shadow="never">
<div class="card-label">基金会员总数</div>
<div class="card-value">
@@ -90,7 +100,12 @@ layout("/layouts/platform.html"){
</el-card>
</el-col>
<el-col v-loading="loading.memberNumberYear">
<el-col class="dashboard-drill" v-loading="loading.memberNumberYear"
title="点击查看今年新加入会员明细"
@click.native="openDetail('今年新加入基金会员明细', 'member', {
startTime: moment().startOf('year').format('YYYY-MM-DD'),
endTime: moment().endOf('year').format('YYYY-MM-DD')
})">
<el-card shadow="never">
<div class="card-label">今年新加入会员数</div>
<div class="card-value">
@@ -99,7 +114,9 @@ layout("/layouts/platform.html"){
</div>
</el-card>
</el-col>
<el-col v-loading="loading.aidMedical">
<el-col class="dashboard-drill" v-loading="loading.aidMedical"
title="点击查看医疗补助申请明细"
@click.native="openDetail('医疗补助申请明细', 'medical', {medicalType: 1})">
<el-card shadow="never">
<div class="card-label">医疗补助申请总数</div>
<div class="card-value">
@@ -108,7 +125,9 @@ layout("/layouts/platform.html"){
</div>
</el-card>
</el-col>
<el-col v-loading="loading.aidMedical">
<el-col class="dashboard-drill" v-loading="loading.aidMedical"
title="点击查看重疾补助申请明细"
@click.native="openDetail('重疾补助申请明细', 'medical', {medicalType: 2})">
<el-card shadow="never">
<div class="card-label">重疾补助申请总数</div>
<div class="card-value">
@@ -117,7 +136,9 @@ layout("/layouts/platform.html"){
</div>
</el-card>
</el-col>
<el-col v-loading="loading.aidMedicalMoney">
<el-col class="dashboard-drill" v-loading="loading.aidMedicalMoney"
title="点击查看已补助申请明细"
@click.native="openDetail('已补助金额明细', 'medical', {stateId: 700})">
<el-card shadow="never">
<div class="card-label">共计补助</div>
<div class="card-value">
@@ -132,10 +153,13 @@ layout("/layouts/platform.html"){
<el-col class="center-col">
<div class="card-label">工会基金会员数</div>
<div style="width: 100%;height: 100px" id="unionMemberChart"></div>
<el-table :data="unionMember" style="width: 100%;margin-top: 20px"
<div class="dashboard-drill" title="点击趋势点查看工会基金会员明细"
style="width: 100%;height: 100px" id="unionMemberChart"></div>
<el-table :data="unionMember" style="width: 100%;margin-top: 20px;cursor: pointer"
height="calc(100vh - 160px - 300px)"
:header-cell-style="{background:'#FAFAFA'}" row-key="id">
:header-cell-style="{background:'#FAFAFA'}" row-key="id"
title="点击工会行查看基金会员明细"
@row-click="openUnionMemberDetail">
<el-table-column label="序号" width="100px">
<template scope="{$index}">
<div class="idx idx-rank" v-if="$index<3">{{$index+1}}</div>
@@ -157,13 +181,15 @@ layout("/layouts/platform.html"){
<el-col class="center-col">
<div class="card-label">在职状态</div>
<div style="width: 100%;height:calc(50% - 35px);box-sizing: border-box;padding: 30px 10px"
<div class="dashboard-drill" title="点击图表项查看在职状态明细"
style="width: 100%;height:calc(50% - 35px);box-sizing: border-box;padding: 30px 10px"
v-loading="loading.userStateMember"
id="userStateMemberChart"></div>
<div class="card-label">人员类型</div>
<div style="width: 100%;height:calc(50% - 35px);box-sizing: border-box;padding: 30px 10px"
<div class="dashboard-drill" title="点击图表项查看人员类型明细"
style="width: 100%;height:calc(50% - 35px);box-sizing: border-box;padding: 30px 10px"
v-loading="loading.personTypeMember"
id="personTypeMemberChart"></div>
@@ -172,6 +198,7 @@ layout("/layouts/platform.html"){
</el-row>
</template>
</guava>
<data-board-detail ref="dataBoardDetail" :base-url="loc()"></data-board-detail>
</div>
<script>
@@ -207,9 +234,27 @@ layout("/layouts/platform.html"){
}
},
components: {
'guava': httpVueLoader('/components/plugins/Guava.vue?v=1.0.0')
'guava': httpVueLoader('/components/plugins/Guava.vue?v=1.0.0'),
'data-board-detail': httpVueLoader('/components/dashboard/DataBoardDetail.vue?v=1.0.3')
},
methods: {
openDetail(title, detailType, filters) {
this.$refs.dataBoardDetail.open(title, detailType, filters)
},
openUnionMemberDetail(row) {
this.openDetail(row.unionname + '基金会员明细', 'member', {
unionId: row.unionid
})
},
getTinyAreaRow(event, rows) {
const point = event && event.data ? event.data.data : null
const index = point && point.x !== undefined ? Number(point.x) : NaN
if (Number.isInteger(index) && rows[index]) {
return rows[index]
}
const value = point && point.y !== undefined ? point.y : point
return rows.find(item => String(item.value) === String(value))
},
/**
* 各工会会员数
* @returns {Promise<void>}
@@ -244,6 +289,12 @@ layout("/layouts/platform.html"){
},
})
chart.unionMemberChart.render();
chart.unionMemberChart.on('element:click', event => {
const row = this.getTinyAreaRow(event, chartData)
if (row) {
this.openUnionMemberDetail(row)
}
})
})
},
@@ -303,6 +354,14 @@ layout("/layouts/platform.html"){
area: {},*/
});
chart.userStateMemberChart.render();
chart.userStateMemberChart.on('element:click', event => {
const row = event && event.data ? event.data.data : null
if (row) {
this.openDetail(row.type + '基金会员明细', 'member', {
userState: row.type
})
}
})
})
@@ -353,6 +412,14 @@ layout("/layouts/platform.html"){
},
});
chart.personTypeMemberChart.render();
chart.personTypeMemberChart.on('element:click', event => {
const row = event && event.data ? event.data.data : null
if (row) {
this.openDetail(row.label + '基金会员明细', 'member', {
personType: row.label
})
}
})
})
@@ -0,0 +1,549 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava @vchange="guavaViewChange" ref="guava">
<template>
<el-card shadow="never">
<div slot="header">
<span>分级核对任务</span>
<el-button @click="uploadDialogVisible=true" icon="el-icon-upload2"
size="small" style="float:right" type="primary">上传并分发</el-button>
</div>
<el-alert :closable="false" show-icon style="margin-bottom:15px"
title="系统仅按 Excel 的“所在单位”列分发,文件名不参与匹配。"></el-alert>
<el-form :inline="true" :model="taskSearch" size="small">
<el-form-item label="任务名称">
<el-input clearable placeholder="请输入任务名称" v-model="taskSearch.taskName"></el-input>
</el-form-item>
<el-form-item label="任务日期">
<el-date-picker clearable format="yyyy-MM-dd" placeholder="请选择任务日期" type="date"
value-format="yyyy-MM-dd" v-model="taskSearch.taskDate"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button @click="loadTasks(true)" icon="el-icon-search" type="primary">查询</el-button>
<el-button @click="resetTaskSearch">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="tasks" border v-loading="loading">
<el-table-column label="任务名称" prop="name"></el-table-column>
<el-table-column align="center" label="任务日期" min-width="160">
<template slot-scope="scope">{{formatDateTime(scope.row.taskDate)}}</template>
</el-table-column>
<el-table-column align="center" label="完成进度" width="180">
<template slot-scope="scope">{{scope.row.finishedUnits || 0}} / {{scope.row.totalUnits || 0}}</template>
</el-table-column>
<el-table-column label="任务说明" prop="description" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="操作" width="300">
<template slot-scope="scope">
<el-button @click="openUnits(scope.row)" size="mini" type="primary">单位核对明细</el-button>
<el-button @click="openCurrentData(scope.row)" size="mini" type="primary">查看数据</el-button>
<el-button @click="deleteTask(scope.row)" size="mini" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination :current-page="taskPage.pageNumber" :page-size="taskPage.pageSize"
:page-sizes="[10,20,50,100]" :total="taskPage.totalCount"
@current-change="taskPageNumberChange" @size-change="taskPageSizeChange"
layout="total, sizes, prev, pager, next, jumper"
style="margin-top:15px;text-align:right"></el-pagination>
</el-card>
</template>
<template #edit_func>
<el-button @click="exportCurrent(dataTarget)" icon="el-icon-download"
size="small" type="primary"
v-if="detailView==='data' && dataScope==='task'">导出生效数据</el-button>
<template v-if="detailView==='data' && dataScope==='unit'">
<el-button @click="download(dataTarget,'original')" icon="el-icon-download"
size="small">下载原始数据</el-button>
<el-button @click="download(dataTarget,'pending')" icon="el-icon-download"
size="small" v-if="dataTarget.state===2">下载修改版</el-button>
<el-button @click="download(dataTarget,'current')" icon="el-icon-download"
size="small" type="primary"
v-if="dataVersionType==='current' && dataTarget.state===4">导出已核对版</el-button>
</template>
</template>
<template #edit>
<div key="unit-detail" v-if="detailView==='units'">
<el-divider content-position="left">{{currentTask.name}} - 单位核对明细</el-divider>
<el-form :inline="true" :model="unitSearch" size="small">
<el-form-item label="单位名称">
<el-input clearable placeholder="请输入单位名称" v-model="unitSearch.unitName"></el-input>
</el-form-item>
<el-form-item label="核对状态">
<el-select clearable placeholder="全部" v-model="unitSearch.state">
<el-option :key="index" :label="name" :value="index"
v-for="(name,index) in statusOptions"></el-option>
</el-select>
</el-form-item>
<el-form-item label="核对人员状态">
<el-select clearable placeholder="全部" v-model="unitSearch.managerState">
<el-option label="已分配核对人员" :value="1"></el-option>
<el-option label="未分配核对人员" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="loadUnitTasks(true)" icon="el-icon-search" type="primary">查询</el-button>
<el-button @click="resetUnitSearch">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="unitTasks" border v-loading="unitLoading">
<el-table-column label="单位" prop="unitName" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="行数" prop="rowCount" width="90"></el-table-column>
<el-table-column align="center" label="状态" width="120">
<template slot-scope="scope">
<el-tag :type="statusType(scope.row.state)">{{statusText(scope.row.state)}}</el-tag>
</template>
</el-table-column>
<el-table-column label="核对人员" min-width="180" show-overflow-tooltip>
<template slot-scope="scope">
<span v-if="scope.row.managerNames || scope.row.managernames">
{{scope.row.managerNames || scope.row.managernames}}
</span>
<el-tag type="warning" v-else>未分配核对人员</el-tag>
</template>
</el-table-column>
<el-table-column label="审核意见" prop="auditOpinion" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="核对日期" min-width="160">
<template slot-scope="scope">
{{formatDateTime(scope.row.checkedAt)}}
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="450">
<template slot-scope="scope">
<el-button @click="openUnitData(scope.row)" size="mini" type="primary">查看原始数据</el-button>
<template v-if="scope.row.state===2">
<el-button @click="openUnitData(scope.row,'pending')" size="mini" type="primary">查看修改版</el-button>
<el-button @click="audit(scope.row,true)" size="mini" type="success">通过</el-button>
<el-button @click="audit(scope.row,false)" size="mini" type="danger">退回</el-button>
</template>
</template>
</el-table-column>
</el-table>
<el-pagination :current-page="unitPage.pageNumber" :page-size="unitPage.pageSize"
:page-sizes="[10,20,50,100]" :total="unitPage.totalCount"
@current-change="unitPageNumberChange" @size-change="unitPageSizeChange"
layout="total, sizes, prev, pager, next, jumper"
style="margin-top:15px;text-align:right"></el-pagination>
</div>
<div key="data-detail" v-else>
<el-divider content-position="left">{{dataDialogTitle}}</el-divider>
<el-form :inline="true" :model="dataSearch" size="small">
<el-form-item>
<el-input @keyup.enter.native="loadDataRows(true)" clearable placeholder="请输入查询内容"
v-model="dataSearch.searchKeyword">
<el-select placeholder="查询字段" slot="prepend" style="width:120px"
v-model="dataSearch.searchName">
<el-option label="薪酬单位代码" value="salaryUnitCode"></el-option>
<el-option label="职工号" value="employeeNo"></el-option>
<el-option label="姓名" value="employeeName"></el-option>
<el-option label="扣工会会费" value="unionFee"></el-option>
<el-option label="年月" value="yearMonth"></el-option>
<el-option label="所在单位" value="unitName"></el-option>
</el-select>
</el-input>
</el-form-item>
<el-form-item label="核对情况">
<el-select clearable placeholder="全部" v-model="dataSearch.state">
<el-option :key="index" :label="name" :value="index"
v-for="(name,index) in statusOptions"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="loadDataRows(true)" icon="el-icon-search" type="primary">查询</el-button>
<el-button @click="resetDataSearch">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="dataRows" border v-loading="dataLoading">
<el-table-column align="center" label="序号" width="70">
<template slot-scope="scope">
{{scope.$index + (dataPage.pageNumber - 1) * dataPage.pageSize + 1}}
</template>
</el-table-column>
<el-table-column label="薪酬单位代码" min-width="130" prop="薪酬单位代码" show-overflow-tooltip></el-table-column>
<el-table-column label="职工号" min-width="120" prop="职工号" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" min-width="100" prop="姓名" show-overflow-tooltip></el-table-column>
<el-table-column label="扣工会会费" min-width="120" prop="扣工会会费" show-overflow-tooltip></el-table-column>
<el-table-column label="年月" min-width="100" prop="年月" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" min-width="180" prop="所在单位" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="核对情况" min-width="110">
<template slot-scope="scope">
<el-tag :type="statusType(scope.row.state)">{{statusText(scope.row.state)}}</el-tag>
</template>
</el-table-column>
<el-table-column label="核对人员" min-width="180" show-overflow-tooltip>
<template slot-scope="scope">
<span v-if="scope.row.managerNames || scope.row.managernames">
{{scope.row.managerNames || scope.row.managernames}}
</span>
<el-tag type="warning" v-else>未分配核对人员</el-tag>
</template>
</el-table-column>
<el-table-column label="核对日期" min-width="160">
<template slot-scope="scope">
{{formatDateTime(scope.row.checkedAt)}}
</template>
</el-table-column>
</el-table>
<el-pagination :current-page="dataPage.pageNumber" :page-size="dataPage.pageSize"
:page-sizes="[10,20,50,100]" :total="dataPage.totalCount"
@current-change="dataPageNumberChange" @size-change="dataPageSizeChange"
layout="total, sizes, prev, pager, next, jumper"
style="margin-top:15px;text-align:right"></el-pagination>
</div>
</template>
</guava>
<el-dialog :close-on-click-modal="false" :visible.sync="uploadDialogVisible" title="上传并分发" width="50%">
<el-form label-width="90px">
<el-form-item label="任务名称" required>
<el-input maxlength="100" v-model="uploadForm.taskName"></el-input>
</el-form-item>
<el-form-item label="任务说明">
<el-input maxlength="500" type="textarea" v-model="uploadForm.description"></el-input>
</el-form-item>
<el-form-item label="Excel文件" required>
<el-upload :action="uploadAction" :auto-upload="false" :data="uploadForm"
:limit="20" :multiple="true" :on-error="uploadError"
:on-success="uploadSuccess" accept=".xls,.xlsx" name="files" ref="upload">
<el-button icon="el-icon-plus" size="small">选择文件</el-button>
<div class="el-upload__tip" slot="tip">可同时选择多个文件,数据按“所在单位”首段归组。</div>
</el-upload>
<el-button @click="downloadTemplate" icon="el-icon-download"
size="small" style="margin-top:10px">下载导入模板</el-button>
</el-form-item>
</el-form>
<span class="dialog-footer" slot="footer">
<el-button @click="uploadDialogVisible=false">取 消</el-button>
<el-button :loading="uploading" @click="submitUpload" type="primary">上传并分发</el-button>
</span>
</el-dialog>
<el-dialog :close-on-click-modal="false" :visible.sync="rejectDialogVisible"
title="退回修改版" width="45%">
<el-form label-width="90px">
<el-form-item label="退回原因" required>
<el-input :rows="5" maxlength="500" placeholder="请输入退回原因"
show-word-limit type="textarea" v-model="rejectOpinion"></el-input>
</el-form-item>
</el-form>
<span class="dialog-footer" slot="footer">
<el-button @click="rejectDialogVisible=false">取 消</el-button>
<el-button :loading="auditSubmitting" @click="submitReject" type="primary">确 定</el-button>
</span>
</el-dialog>
</div>
<script>
const vue = new Vue({
el: '#app',
data() {
return {
loading: false,
unitLoading: false,
uploading: false,
tasks: [],
unitTasks: [],
statusOptions: ['待核对', '已确认', '待审核', '已退回', '已通过'],
taskSearch: {taskName: '', taskDate: ''},
taskPage: {pageNumber: 1, pageSize: 20, totalCount: 0},
unitSearch: {unitName: '', state: null, managerState: null},
unitPage: {pageNumber: 1, pageSize: 20, totalCount: 0},
currentTask: {},
detailView: '',
dataTarget: {},
dataScope: '',
dataVersionType: 'current',
dataRows: [],
dataLoading: false,
dataDialogTitle: '',
dataSearch: {searchName: 'employeeNo', searchKeyword: '', state: null},
dataPage: {pageNumber: 1, pageSize: 10, totalCount: 0},
uploadDialogVisible: false,
uploadForm: {taskName: '', description: ''},
uploadAction: loc() + '/doUpload',
rejectDialogVisible: false,
rejectTarget: {},
rejectOpinion: '',
auditSubmitting: false
}
},
components: {
'guava': httpVueLoader('/components/plugins/Guava.vue')
},
methods: {
formatDateTime(value) {
return value ? moment(value).format('YYYY-MM-DD HH:mm') : '-'
},
statusText(state) {
return ['待核对', '已确认', '待审核', '已退回', '已通过'][state] || '未知'
},
statusType(state) {
return ['', 'success', 'warning', 'danger', 'success'][state] || 'info'
},
async loadTasks(resetPage) {
if (resetPage) this.taskPage.pageNumber = 1
this.loading = true
const resp = await $.post(loc() + '/taskData', {
taskName: this.taskSearch.taskName,
taskDate: this.taskSearch.taskDate,
pageNumber: this.taskPage.pageNumber,
pageSize: this.taskPage.pageSize
})
this.loading = false
if (resp.code === 0) {
const page = resp.data || {}
this.tasks = page.list || []
this.taskPage.totalCount = page.totalCount || 0
} else this.$notify.warning(resp.msg)
},
resetTaskSearch() {
this.taskSearch = {taskName: '', taskDate: ''}
this.loadTasks(true)
},
taskPageNumberChange(value) {
this.taskPage.pageNumber = value
this.loadTasks(false)
},
taskPageSizeChange(value) {
this.taskPage.pageSize = value
this.loadTasks(true)
},
submitUpload() {
if (!this.uploadForm.taskName) {
this.$notify.warning('请输入任务名称')
return
}
if (!this.$refs.upload.uploadFiles.length) {
this.$notify.warning('请选择 Excel 文件')
return
}
this.uploading = true
const formData = new FormData()
formData.append('taskName', this.uploadForm.taskName)
formData.append('description', this.uploadForm.description || '')
this.$refs.upload.uploadFiles.forEach(item => formData.append('files', item.raw))
$.ajax({
url: this.uploadAction,
type: 'POST',
data: formData,
processData: false,
contentType: false,
dataType: 'json',
success: resp => this.uploadSuccess(resp),
error: () => this.uploadError()
})
},
downloadTemplate() {
window.open(loc() + '/downloadTemplate')
},
uploadSuccess(resp) {
this.uploading = false
if (resp.code === 0) {
this.uploadDialogVisible = false
this.$refs.upload.clearFiles()
this.loadTasks(true)
const result = resp.data || {}
if (result.errorCount > 0) {
this.$confirm('共读取 ' + result.totalCount + ' 条,成功分发 ' + result.successCount
+ ' 条,失败 ' + result.errorCount + ' 条。', '导入完成', {
confirmButtonText: '导出错误信息',
cancelButtonText: '关闭',
type: 'warning'
}).then(() => {
window.open(loc() + '/exportImportErrors?cacheKey=' + encodeURIComponent(result.errorCacheKey))
}).catch(() => {})
} else {
this.$notify.success(resp.msg)
}
} else {
this.$notify.warning(resp.msg)
}
},
uploadError() {
this.uploading = false
this.$notify.error('上传失败')
},
async openUnits(row) {
this.currentTask = row
this.detailView = 'units'
this.unitSearch = {unitName: '', state: null, managerState: null}
this.unitPage.pageNumber = 1
this.$refs.guava.edit()
await this.loadUnitTasks(false)
},
guavaViewChange(view) {
if (view.newValue === 'index' && view.oldValue === 'edit'
&& this.detailView === 'data' && this.dataScope === 'unit') {
this.detailView = 'units'
this.$nextTick(() => this.$refs.guava.edit())
}
},
async loadUnitTasks(resetPage) {
if (resetPage) this.unitPage.pageNumber = 1
this.unitLoading = true
const resp = await $.post(loc() + '/unitTaskData', {
taskId: this.currentTask.id,
unitName: this.unitSearch.unitName,
state: this.unitSearch.state,
managerState: this.unitSearch.managerState,
pageNumber: this.unitPage.pageNumber,
pageSize: this.unitPage.pageSize
})
this.unitLoading = false
if (resp.code === 0) {
const page = resp.data || {}
this.unitTasks = page.list || []
this.unitPage.totalCount = page.totalCount || 0
} else this.$notify.warning(resp.msg)
},
resetUnitSearch() {
this.unitSearch = {unitName: '', state: null, managerState: null}
this.loadUnitTasks(true)
},
unitPageNumberChange(value) {
this.unitPage.pageNumber = value
this.loadUnitTasks(false)
},
unitPageSizeChange(value) {
this.unitPage.pageSize = value
this.loadUnitTasks(true)
},
openUnitData(row, versionType) {
this.detailView = 'data'
this.dataScope = 'unit'
this.dataVersionType = versionType || 'current'
this.dataTarget = row
this.dataDialogTitle = row.unitName
+ (this.dataVersionType === 'pending' ? ' - 待审核修改版数据' : ' - 当前生效数据')
this.dataSearch = {searchName: 'employeeNo', searchKeyword: '', state: null}
this.dataPage.pageNumber = 1
this.$refs.guava.edit()
this.loadDataRows(false)
},
openCurrentData(row) {
this.detailView = 'data'
this.dataScope = 'task'
this.dataVersionType = 'current'
this.dataTarget = row
this.dataDialogTitle = row.name + ' - 生效数据'
this.dataSearch = {searchName: 'employeeNo', searchKeyword: '', state: null}
this.dataPage.pageNumber = 1
this.$refs.guava.edit()
this.loadDataRows(false)
},
async loadDataRows(resetPage) {
if (resetPage) this.dataPage.pageNumber = 1
this.dataLoading = true
const url = this.dataScope === 'unit' ? '/unitDataPage' : '/currentDataPage'
const params = {
searchName: this.dataSearch.searchName,
searchKeyword: this.dataSearch.searchKeyword,
state: this.dataSearch.state,
pageNumber: this.dataPage.pageNumber,
pageSize: this.dataPage.pageSize
}
if (this.dataScope === 'unit') {
params.unitTaskId = this.dataTarget.id
params.versionType = this.dataVersionType
}
else params.taskId = this.dataTarget.id
const resp = await $.post(loc() + url, params)
this.dataLoading = false
if (resp.code === 0) {
const page = resp.data || {}
this.dataRows = page.list || []
this.dataPage.totalCount = page.totalCount || 0
} else {
this.dataRows = []
this.dataPage.totalCount = 0
this.$notify.warning(resp.msg)
}
},
resetDataSearch() {
this.dataSearch = {searchName: 'employeeNo', searchKeyword: '', state: null}
this.loadDataRows(true)
},
dataPageNumberChange(value) {
this.dataPage.pageNumber = value
this.loadDataRows(false)
},
dataPageSizeChange(value) {
this.dataPage.pageSize = value
this.loadDataRows(true)
},
download(row, versionType) {
window.open(loc() + '/download?unitTaskId=' + row.id + '&versionType=' + versionType)
},
exportCurrent(row) {
window.open(loc() + '/exportCurrent?taskId=' + row.id)
},
async deleteTask(row) {
try {
await this.$confirm('删除后,该任务的单位数据、修改数据和审核记录都将被清除,且无法恢复。',
'确认删除“' + row.name + '”?', {
confirmButtonText: '确认删除',
cancelButtonText: '取消',
type: 'warning'
})
} catch (e) {
return
}
const resp = await $.post(loc() + '/doDelete', {taskId: row.id})
if (resp.code === 0) {
this.$notify.success(resp.msg)
if (this.currentTask.id === row.id) {
this.$refs.guava.index()
this.currentTask = {}
this.unitTasks = []
}
if (this.dataScope === 'task' && this.dataTarget.id === row.id) {
this.$refs.guava.index()
this.dataTarget = {}
this.dataRows = []
}
this.loadTasks(false)
} else this.$notify.warning(resp.msg)
},
async audit(row, approved) {
if (!approved) {
this.rejectTarget = row
this.rejectOpinion = ''
this.rejectDialogVisible = true
return
}
await this.submitAudit(row, true, '')
},
async submitReject() {
const opinion = (this.rejectOpinion || '').trim()
if (!opinion) {
this.$notify.warning('请输入退回原因')
return
}
this.auditSubmitting = true
const success = await this.submitAudit(this.rejectTarget, false, opinion)
this.auditSubmitting = false
if (success) this.rejectDialogVisible = false
},
async submitAudit(row, approved, opinion) {
const resp = await $.post(loc() + '/doAudit', {unitTaskId: row.id, approved, opinion})
if (resp.code === 0) {
this.$notify.success(resp.msg)
await this.loadUnitTasks(false)
this.loadTasks(false)
return true
}
this.$notify.warning(resp.msg)
return false
}
},
created() {
this.loadTasks(false)
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,318 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<template>
<el-card shadow="never">
<div slot="header">我的分级核对任务</div>
<el-alert :closable="false" show-icon style="margin-bottom:15px" type="warning"
title="温馨提示:请先下载或查看核对数据。若数据有误,请按原模板修改后上传修改版,提交后进入校工会审核;若数据无误,无需上传文件,进入“核对数据”页面点击“确认无误”即可完成核对。"></el-alert>
<el-form :inline="true" :model="searchForm" size="small">
<el-form-item label="任务/单位">
<el-input clearable placeholder="请输入任务名称、单位或任务日期" v-model="searchForm.keyword"></el-input>
</el-form-item>
<el-form-item label="核对状态">
<el-select clearable placeholder="全部" v-model="searchForm.state">
<el-option :key="index" :label="name" :value="index"
v-for="(name,index) in statusOptions"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="loadTasks(true)" icon="el-icon-search" type="primary">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="tasks" border v-loading="loading">
<el-table-column label="任务名称" prop="taskName" show-overflow-tooltip></el-table-column>
<el-table-column label="单位" prop="unitName" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="任务日期" min-width="160">
<template slot-scope="scope">{{formatDateTime(scope.row.taskDate || scope.row.taskdate)}}</template>
</el-table-column>
<el-table-column align="center" label="核对日期" min-width="160">
<template slot-scope="scope">
{{formatDateTime(scope.row.checkedAt)}}
</template>
</el-table-column>
<el-table-column align="center" label="行数" prop="rowCount" width="80"></el-table-column>
<el-table-column align="center" label="状态" width="110">
<template slot-scope="scope">
<el-tag :type="statusType(scope.row.state)">{{statusText(scope.row.state)}}</el-tag>
</template>
</el-table-column>
<el-table-column label="审核意见" prop="auditOpinion" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="操作" width="350">
<template slot-scope="scope">
<el-button @click="openData(scope.row)" size="mini" type="primary">核对数据</el-button>
<el-button @click="download(scope.row)" size="mini">下载数据</el-button>
<!--<el-button @click="confirm(scope.row)" size="mini" type="success"
v-if="scope.row.state===0">确认无误</el-button>-->
<el-button @click="openRevision(scope.row)" size="mini" type="primary"
v-if="scope.row.state===0 || scope.row.state===3">上传修改版</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination :current-page="pageForm.pageNumber" :page-size="pageForm.pageSize"
:page-sizes="[10,20,50,100]" :total="pageForm.totalCount"
@current-change="pageNumberChange" @size-change="pageSizeChange"
layout="total, sizes, prev, pager, next, jumper"
style="margin-top:15px;text-align:right"></el-pagination>
</el-card>
</template>
<template #edit_func>
<el-button @click="confirm(currentTask)" size="small" type="success"
v-if="currentTask.state===0">确认无误</el-button>
<el-button @click="openRevision(currentTask)" size="small" type="primary"
v-if="currentTask.state===0 || currentTask.state===3">上传修改版</el-button>
</template>
<template #edit>
<el-divider content-position="left">{{dataDialogTitle}}</el-divider>
<el-form :inline="true" :model="dataSearch" size="small">
<el-form-item>
<el-input @keyup.enter.native="loadDataRows(true)" clearable placeholder="请输入查询内容"
v-model="dataSearch.searchKeyword">
<el-select placeholder="查询字段" slot="prepend" style="width:120px"
v-model="dataSearch.searchName">
<el-option label="薪酬单位代码" value="salaryUnitCode"></el-option>
<el-option label="职工号" value="employeeNo"></el-option>
<el-option label="姓名" value="employeeName"></el-option>
<el-option label="扣工会会费" value="unionFee"></el-option>
<el-option label="年月" value="yearMonth"></el-option>
<el-option label="所在单位" value="unitName"></el-option>
</el-select>
</el-input>
</el-form-item>
<el-form-item label="核对情况">
<el-select clearable placeholder="全部" v-model="dataSearch.state">
<el-option :key="index" :label="name" :value="index"
v-for="(name,index) in statusOptions"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="loadDataRows(true)" icon="el-icon-search" type="primary">查询</el-button>
<el-button @click="resetDataSearch">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="dataRows" border v-loading="dataLoading">
<el-table-column align="center" label="序号" width="70">
<template slot-scope="scope">
{{scope.$index + (dataPage.pageNumber - 1) * dataPage.pageSize + 1}}
</template>
</el-table-column>
<el-table-column label="薪酬单位代码" min-width="130" prop="薪酬单位代码" show-overflow-tooltip></el-table-column>
<el-table-column label="职工号" min-width="120" prop="职工号" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" min-width="100" prop="姓名" show-overflow-tooltip></el-table-column>
<el-table-column label="扣工会会费" min-width="120" prop="扣工会会费" show-overflow-tooltip></el-table-column>
<el-table-column label="年月" min-width="100" prop="年月" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" min-width="180" prop="所在单位" show-overflow-tooltip></el-table-column>
<el-table-column align="center" label="核对情况" min-width="110">
<template slot-scope="scope">
<el-tag :type="statusType(scope.row.state)">{{statusText(scope.row.state)}}</el-tag>
</template>
</el-table-column>
<el-table-column label="核对人员" min-width="180" show-overflow-tooltip>
<template slot-scope="scope">
<span v-if="scope.row.managerNames || scope.row.managernames">
{{scope.row.managerNames || scope.row.managernames}}
</span>
<el-tag type="warning" v-else>未分配核对人员</el-tag>
</template>
</el-table-column>
<el-table-column label="核对日期" min-width="160">
<template slot-scope="scope">
{{formatDateTime(scope.row.checkedAt)}}
</template>
</el-table-column>
</el-table>
<el-pagination :current-page="dataPage.pageNumber" :page-size="dataPage.pageSize"
:page-sizes="[10,20,50,100]" :total="dataPage.totalCount"
@current-change="dataPageNumberChange" @size-change="dataPageSizeChange"
layout="total, sizes, prev, pager, next, jumper"
style="margin-top:15px;text-align:right"></el-pagination>
</template>
</guava>
<el-dialog :visible.sync="revisionDialogVisible" title="上传修改后的数据" width="45%">
<el-alert :closable="false" show-icon style="margin-bottom:15px" type="warning"
title="修改版必须使用固定导入模板,且只能包含当前单位数据。"></el-alert>
<el-upload :action="revisionAction" :auto-upload="false" :data="revisionData"
:limit="1" :on-error="uploadError" :on-success="uploadSuccess"
accept=".xls,.xlsx" name="file" ref="revisionUpload">
<el-button icon="el-icon-plus" size="small">选择修改版</el-button>
</el-upload>
<span class="dialog-footer" slot="footer">
<el-button @click="revisionDialogVisible=false">取 消</el-button>
<el-button :loading="uploading" @click="submitRevision" type="primary">提 交</el-button>
</span>
</el-dialog>
</div>
<script>
const vue = new Vue({
el: '#app',
data() {
return {
loading: false,
uploading: false,
tasks: [],
currentTask: {},
dataRows: [],
dataLoading: false,
dataDialogTitle: '',
dataSearch: {searchName: 'employeeNo', searchKeyword: '', state: null},
dataPage: {pageNumber: 1, pageSize: 10, totalCount: 0},
statusOptions: ['待核对', '已确认', '待审核', '已退回', '已通过'],
searchForm: {keyword: '', state: null},
pageForm: {pageNumber: 1, pageSize: 10, totalCount: 0},
revisionDialogVisible: false,
revisionAction: loc() + '/doUploadRevision',
revisionData: {unitTaskId: ''}
}
},
components: {
'guava': httpVueLoader('/components/plugins/Guava.vue')
},
methods: {
formatDateTime(value) {
return value ? moment(value).format('YYYY-MM-DD HH:mm') : '-'
},
statusText(state) {
return ['待核对', '已确认', '待审核', '已退回', '已通过'][state] || '未知'
},
statusType(state) {
return ['', 'success', 'warning', 'danger', 'success'][state] || 'info'
},
async loadTasks(resetPage) {
if (resetPage) this.pageForm.pageNumber = 1
this.loading = true
const resp = await $.post(loc() + '/taskData', {
keyword: this.searchForm.keyword,
state: this.searchForm.state,
pageNumber: this.pageForm.pageNumber,
pageSize: this.pageForm.pageSize
})
this.loading = false
if (resp.code === 0) {
const page = resp.data || {}
this.tasks = page.list || []
this.pageForm.totalCount = page.totalCount || 0
} else this.$notify.warning(resp.msg)
},
resetSearch() {
this.searchForm = {keyword: '', state: null}
this.loadTasks(true)
},
pageNumberChange(value) {
this.pageForm.pageNumber = value
this.loadTasks(false)
},
pageSizeChange(value) {
this.pageForm.pageSize = value
this.loadTasks(true)
},
download(row) {
window.open(loc() + '/download?unitTaskId=' + row.id)
},
openData(row) {
this.currentTask = row
this.dataDialogTitle = row.taskName + ' - ' + row.unitName + ' - 数据详情'
this.dataSearch = {searchName: 'employeeNo', searchKeyword: '', state: null}
this.dataPage.pageNumber = 1
this.$refs.guava.edit()
this.loadDataRows(false)
},
async loadDataRows(resetPage) {
if (resetPage) this.dataPage.pageNumber = 1
this.dataLoading = true
const resp = await $.post(loc() + '/dataPage', {
unitTaskId: this.currentTask.id,
searchName: this.dataSearch.searchName,
searchKeyword: this.dataSearch.searchKeyword,
state: this.dataSearch.state,
pageNumber: this.dataPage.pageNumber,
pageSize: this.dataPage.pageSize
})
this.dataLoading = false
if (resp.code === 0) {
const page = resp.data || {}
this.dataRows = page.list || []
this.dataPage.totalCount = page.totalCount || 0
} else {
this.dataRows = []
this.dataPage.totalCount = 0
this.$notify.warning(resp.msg)
}
},
resetDataSearch() {
this.dataSearch = {searchName: 'employeeNo', searchKeyword: '', state: null}
this.loadDataRows(true)
},
dataPageNumberChange(value) {
this.dataPage.pageNumber = value
this.loadDataRows(false)
},
dataPageSizeChange(value) {
this.dataPage.pageSize = value
this.loadDataRows(true)
},
async confirm(row) {
try {
await this.$confirm('确认本单位数据无误吗?确认后不能再上传修改数据,无修改内容时不会进入审核流程!', '提示', {type: 'warning'})
} catch (e) {
return
}
const resp = await $.post(loc() + '/doConfirm', {unitTaskId: row.id})
if (resp.code === 0) {
this.$notify.success(resp.msg)
this.$refs.guava.index()
this.loadTasks(false)
} else this.$notify.warning(resp.msg)
},
openRevision(row) {
this.revisionData.unitTaskId = row.id
this.revisionDialogVisible = true
this.$nextTick(() => this.$refs.revisionUpload.clearFiles())
},
submitRevision() {
if (!this.$refs.revisionUpload.uploadFiles.length) {
this.$notify.warning('请选择修改版 Excel')
return
}
this.uploading = true
const formData = new FormData()
formData.append('unitTaskId', this.revisionData.unitTaskId)
formData.append('file', this.$refs.revisionUpload.uploadFiles[0].raw)
$.ajax({
url: this.revisionAction,
type: 'POST',
data: formData,
processData: false,
contentType: false,
dataType: 'json',
success: resp => this.uploadSuccess(resp),
error: () => this.uploadError()
})
},
uploadSuccess(resp) {
this.uploading = false
if (resp.code === 0) {
this.$notify.success(resp.msg)
this.revisionDialogVisible = false
this.$refs.guava.index()
this.loadTasks(false)
} else this.$notify.warning(resp.msg)
},
uploadError() {
this.uploading = false
this.$notify.error('上传失败')
}
},
created() {
this.loadTasks(false)
}
})
</script>
<!--#
}
#-->
@@ -288,7 +288,8 @@
pageForm: {
source: source,
searchName: "u.username",
audit: 2
// 已办入口默认显示本阶段已审核记录。
audit: new URLSearchParams(window.location.search).get("audit") === "1" ? 1 : 2
},
unions: [],
tableColumns: [
@@ -67,12 +67,22 @@ layout("/layouts/platform.html"){
overflow-y: auto;
}
.dashboard-drill {
cursor: pointer;
}
.dashboard-drill:hover .top-col-member-number,
.dashboard-drill:hover .col-title {
color: #409EFF;
}
</style>
<div id="app" v-cloak>
<guava>
<el-row style="background-color: #f0f2f5" type="flex">
<el-col class="top-col" v-loading="loading.memberNumber">
<el-col class="top-col dashboard-drill" v-loading="loading.memberNumber"
title="点击查看会员明细"
@click.native.capture="openMemberDetail('会员总数明细', {year: moment().format('YYYY')})">
<div class="col-title">会员数</div>
<div class="top-col-member-number">{{memberNumberData.memberNum}}<span
style="font-size: 14px">&ensp;人</span></div>
@@ -97,16 +107,20 @@ layout("/layouts/platform.html"){
</el-col>
<el-col class="top-col" v-loading="loading.growthTrend">
<div class="col-title">增长趋势</div>
<div class="top-col-chart" id="growthTrendChart"></div>
<div class="top-col-chart dashboard-drill" title="点击趋势点查看年度明细"
id="growthTrendChart"></div>
</el-col>
<el-col class="top-col" v-loading="loading.memberPercentage">
<el-col class="top-col dashboard-drill" v-loading="loading.memberPercentage"
title="点击查看会员明细"
@click.native.capture="openMemberDetail('会员占比明细', {year: moment().format('YYYY')})">
<div class="col-title">会员占比</div>
<div class="top-col-chart" id="memberPercentageChart"
style="position: absolute;height: 120px;right: -10%;bottom: 20px"></div>
</el-col>
<el-col class="top-col" v-loading="loading.growthTrend">
<div class="col-title">男女占比</div>
<div class="top-col-chart" id="memberSexPercentageChart"
<div class="top-col-chart dashboard-drill" title="点击图表项查看性别明细"
id="memberSexPercentageChart"
style="position: absolute;height: 120px;right: -10%;bottom: 20px"></div>
</el-col>
</el-row>
@@ -119,7 +133,9 @@ layout("/layouts/platform.html"){
<el-table :data="unionMember" :header-cell-style="{background:'#FAFAFA'}"
height="calc(100vh - 160px - 300px)"
row-key="id" style="width: 100%;margin-top: 20px">
row-key="id" style="width: 100%;margin-top: 20px;cursor: pointer"
title="点击工会行查看会员明细"
@row-click="openUnionMemberDetail">
<el-table-column label="序号" width="100px">
<template scope="{$index}">
<div class="idx idx-rank" v-if="$index<3">{{$index+1}}</div>
@@ -152,6 +168,7 @@ layout("/layouts/platform.html"){
</el-col>
</el-row>
</guava>
<data-board-detail ref="dataBoardDetail" :base-url="loc()"></data-board-detail>
</div>
@@ -193,8 +210,28 @@ layout("/layouts/platform.html"){
unionMember: [],
}
},
components: {},
components: {
'data-board-detail': httpVueLoader('/components/dashboard/DataBoardDetail.vue?v=1.0.3')
},
methods: {
openMemberDetail(title, filters) {
this.$refs.dataBoardDetail.open(title, 'member', filters)
},
openUnionMemberDetail(row) {
this.openMemberDetail(row.unionname + '会员明细', {
year: moment().format('YYYY'),
unionId: row.unionid
})
},
getTinyAreaRow(event, rows) {
const point = event && event.data ? event.data.data : null
const index = point && point.x !== undefined ? Number(point.x) : NaN
if (Number.isInteger(index) && rows[index]) {
return rows[index]
}
const value = point && point.y !== undefined ? point.y : point
return rows.find(item => String(item.value) === String(value))
},
/**
* 会员数
* @returns {Promise<void>}
@@ -247,6 +284,12 @@ layout("/layouts/platform.html"){
},
});
chart.growthTrendChart.render();
chart.growthTrendChart.on('element:click', event => {
const row = this.getTinyAreaRow(event, data)
if (row) {
this.openMemberDetail(row.label + '年会员明细', {year: row.label})
}
})
})
},
@@ -321,6 +364,15 @@ layout("/layouts/platform.html"){
interactions: [{type: 'element-active'}], color: ['#409EFF', '#F56C6C'],
});
chart.memberSexPercentageChart.render();
chart.memberSexPercentageChart.on('element:click', event => {
const row = event && event.data ? event.data.data : null
if (row) {
this.openMemberDetail(row.type + '性会员明细', {
year: moment().format('YYYY'),
sex: row.type
})
}
})
})
},
@@ -358,6 +410,12 @@ layout("/layouts/platform.html"){
},
})
chart.unionMemberChart.render();
chart.unionMemberChart.on('element:click', event => {
const row = this.getTinyAreaRow(event, chartData)
if (row) {
this.openUnionMemberDetail(row)
}
})
})
},
@@ -407,6 +465,15 @@ layout("/layouts/platform.html"){
},
});
chart.personTypeMemberChart.render();
chart.personTypeMemberChart.on('element:click', event => {
const row = event && event.data ? event.data.data : null
if (row) {
this.openMemberDetail(row.label + '会员明细', {
year: moment().format('YYYY'),
personType: row.label
})
}
})
})
@@ -456,6 +523,15 @@ layout("/layouts/platform.html"){
area: {},
});
chart.userStateMemberChart.render();
chart.userStateMemberChart.on('element:click', event => {
const row = event && event.data ? event.data.data : null
if (row) {
this.openMemberDetail(row.label + '会员明细', {
year: moment().format('YYYY'),
userState: row.label
})
}
})
})
@@ -32,7 +32,8 @@ layout("/layouts/platform.html"){
<table-tool label="提案信息" :app="this">
<template #func>
<el-button @click="exportAllProposal" type="primary" size="small">导出立案汇总表</el-button>
<el-button @click="exportCompleteData" type="primary" size="small">按条件导出完整数据</el-button>
<el-button @click="exportAllProposal" type="success" size="small">导出原立案汇总表</el-button>
</template>
</table-tool>
@@ -121,6 +122,7 @@ layout("/layouts/platform.html"){
mixins: [initTableMixins],
data() {
return {
showStateSearch: true,
resultOptions: [],
determineTypeOptions: [],
typeOptions: [],
@@ -138,6 +140,7 @@ layout("/layouts/platform.html"){
meetingId: '',
unionId: '',
unitId: '',
undertakeId: '',
delegationId: '',
proposalResult: [],
proposalTypes: [],
@@ -166,42 +169,50 @@ layout("/layouts/platform.html"){
'proposal-table': httpVueLoader('/components/proposal/ProposalTable.vue'),
},
methods: {
exportCompleteData() {
proposal.exportCompleteData(this, {title: '立案提案完整数据', caseOnly: true})
},
exportAllProposal() {
const proposalTypes = JSON.stringify(this.pageForm.proposalTypes)
const proposalResult = JSON.stringify(this.pageForm.proposalResult)
const searchName2 = this.pageForm.searchName2
const searchKeyword2 = this.pageForm.searchKeyword2
const stateCode = this.pageForm.stateCode
const searchKeyword = this.pageForm.searchKeyword
const searchName = this.pageForm.searchName
const delegationId = this.pageForm.delegationId
const unionId = this.pageForm.unionId
const unitId = this.pageForm.unitId
const meetingId = this.pageForm.meetingId
const {exportCondition, exportFileCondition} = proposal.buildExportCondition(this)
let props = {}
this.tableColumns.forEach(v => {
props[v.prop] = v.label
})
window.open(loc() + "/exportAllProposal?proposalTypes=" + proposalTypes +
"&proposalResult=" + proposalResult +
"&searchName2=" + searchName2 +
"&searchKeyword2=" + searchKeyword2 +
"&stateCode=" + stateCode +
"&searchKeyword=" + searchKeyword +
"&searchName=" + searchName +
"&delegationId=" + delegationId +
"&unionId=" + unionId +
"&unitId=" + unitId +
"&meetingId=" + meetingId +
"&props=" + JSON.stringify(props))
this.$confirm('将按当前条件导出全部 ' + (this.pageForm.totalCount || 0)
+ ' 条立案汇总数据,不受当前分页影响。查询条件:' + exportCondition, '导出确认', {
confirmButtonText: '确认导出',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const exportForm = $('<form method="post" target="_blank"></form>')
exportForm.attr('action', loc() + '/exportAllProposal')
const params = Object.assign({}, this.pageForm, {
proposalTypes: JSON.stringify(this.pageForm.proposalTypes),
proposalResult: JSON.stringify(this.pageForm.proposalResult),
determineTypeOptions: JSON.stringify(this.pageForm.proposalDetermineTypes),
props: JSON.stringify(props),
exportCondition,
exportFileCondition
})
Object.keys(params).forEach(key => {
if (!Array.isArray(params[key])) {
exportForm.append($('<input type="hidden">').attr('name', key).val(params[key] || ''))
}
})
$('body').append(exportForm)
exportForm.submit()
exportForm.remove()
}).catch(() => {})
},
oneProposalExport(id) {
window.location.href = "/platform/proposal/statistics/summary/oneProposalExport?proposalId=" + id
},
async meetingChange(val) {
this.pageForm.delegationId = ''
this.delegationOptions = await proposal.getDelegation(val)
this.undertakeOptions = await proposal.getUndertakeByMeeting(val)
this.pageForm.undertakeId = ''
this.delegationOptions = val ? await proposal.getDelegation(val) : []
this.undertakeOptions = val ? await proposal.getUndertakeByMeeting(val)
: await proposal.getAllProposalUndertake()
this.doSearch()
},
async delegationChange(val) {
@@ -271,8 +282,9 @@ layout("/layouts/platform.html"){
async created() {
this.meetingOptions = await getAllJdh()
this.pageForm.meetingId = this.meetingOptions.length ? this.meetingOptions[0].id : ''
this.meetingOptions.unshift({id: '', jdhallname: '全部届次'})
this.stateOptions = await proposal.getOpenProposalState()
this.stateOptions = await proposal.getProposalState()
this.typeOptions = await proposal.getProposalType()
this.resultOptions = await getDictOptions("proposal_result")
this.determineTypeOptions = await getDictOptions("proposal_determine_type")

Some files were not shown because too many files have changed in this diff Show More