commit
@@ -2,7 +2,7 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.budwk</groupId>
|
||||
<artifactId>zhgh_jshvc</artifactId>
|
||||
<artifactId>mini</artifactId>
|
||||
<version>5.6.0-plus</version>
|
||||
<build>
|
||||
<resources>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.zhgh.activity.declarereimbursement.reimbursement.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
@@ -75,6 +76,12 @@ public class ActivityReimbursementApplyController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@SaCheckPermission("h5.activityReimbursement.apply")
|
||||
@Ok("beetl:/platform/zhghh5/activity/declarereimbursement/reimbursement/apply/index.html")
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("activityReimbursement.apply")
|
||||
@Ok("beetl:/platform/zhgh/activity/declarereimbursement/reimbursement/apply/form.html")
|
||||
@@ -84,7 +91,7 @@ public class ActivityReimbursementApplyController {
|
||||
|
||||
@At
|
||||
@ApiOperation("保存申请")
|
||||
@SaCheckPermission("member.apply.submit")
|
||||
@SaCheckPermission(value = {"activityReimbursement.apply", "h5.activityReimbursement.apply"}, mode = SaMode.OR)
|
||||
@SLog(tag = "活动申报", msg = "保存申请,申请人: ${args[0].username}")
|
||||
public Result save(@Param("data") ActivityReimbursementInfo activityReimbursementInfo) {
|
||||
activityReimbursementInfo.setUserId(SecurityUtil.getUserId());
|
||||
@@ -113,7 +120,7 @@ public class ActivityReimbursementApplyController {
|
||||
@At
|
||||
@ApiOperation("提交申请")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("member.apply.submit")
|
||||
@SaCheckPermission(value = {"activityReimbursement.apply", "h5.activityReimbursement.apply"}, mode = SaMode.OR)
|
||||
@SLog(tag = "活动申报", msg = "提交申请,申请人: ${args[0].username}")
|
||||
public Result submit(@Param("data") ActivityReimbursementInfo activityReimbursementInfo) {
|
||||
activityReimbursementInfo.setUserId(SecurityUtil.getUserId());
|
||||
@@ -160,7 +167,7 @@ public class ActivityReimbursementApplyController {
|
||||
@At
|
||||
@ApiOperation("重新提交申请")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("member.apply.submit")
|
||||
@SaCheckPermission(value = {"activityReimbursement.apply", "h5.activityReimbursement.apply"}, mode = SaMode.OR)
|
||||
@SLog(tag = "活动申报", msg = "重新提交申请,申请人: ${args[0].username}")
|
||||
public Result submitAgain(@Param("data") ActivityReimbursementInfo activityReimbursementInfo, @Param("taskId") Long taskId) {
|
||||
if (StrUtil.isBlank(activityReimbursementInfo.getDeclareId())) {
|
||||
@@ -191,7 +198,7 @@ public class ActivityReimbursementApplyController {
|
||||
|
||||
@At
|
||||
@ApiOperation("获取当前用户活动报销")
|
||||
@SaCheckPermission("activityReimbursement.apply")
|
||||
@SaCheckPermission(value = {"activityReimbursement.apply", "h5.activityReimbursement.apply"}, mode = SaMode.OR)
|
||||
public Result getActivityReimbursementByUser(String id, String outlayManageSource, String clubId) {
|
||||
String activityType = "";
|
||||
if (StrUtil.isNotBlank(id)) {
|
||||
@@ -286,7 +293,7 @@ public class ActivityReimbursementApplyController {
|
||||
|
||||
@At
|
||||
@ApiOperation("获取收款人卡号")
|
||||
@SaCheckPermission("activityReimbursement.apply")
|
||||
@SaCheckPermission(value = {"activityReimbursement.apply", "h5.activityReimbursement.apply"}, mode = SaMode.OR)
|
||||
public Result getCardNumberByPayeeId(String username) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
@@ -315,7 +322,7 @@ public class ActivityReimbursementApplyController {
|
||||
|
||||
@At
|
||||
@ApiOperation("获取申报的记录")
|
||||
@SaCheckPermission("activityReimbursement.apply")
|
||||
@SaCheckPermission(value = {"activityReimbursement.apply", "h5.activityReimbursement.apply"}, mode = SaMode.OR)
|
||||
public Result getBudgetByYear(String outlayManageSource, String clubId) {
|
||||
List<NutMap> budgetMoney = activityReimbursementService.getBudgetMoney(outlayManageSource, clubId);
|
||||
return Result.success(budgetMoney);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.zhgh.activity.declarereimbursement.reimbursement.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.result.Result;
|
||||
@@ -13,6 +14,7 @@ import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.Exps;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
@@ -43,6 +45,12 @@ public class ActivityReimbursementBranchUnionController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@SaCheckPermission("h5.activityReimbursement.branchUnion")
|
||||
@Ok("beetl:/platform/zhghh5/activity/declarereimbursement/reimbursement/branchUnion/index.html")
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("activityReimbursement.branchUnion")
|
||||
@Ok("beetl:/platform/zhgh/activity/declarereimbursement/reimbursement/branchunion/form.html")
|
||||
@@ -52,7 +60,7 @@ public class ActivityReimbursementBranchUnionController {
|
||||
|
||||
@At
|
||||
@ApiOperation("活动报销,分工会审核列表")
|
||||
@SaCheckPermission("activityReimbursement.branchUnion")
|
||||
@SaCheckPermission(value = {"activityReimbursement.branchUnion", "h5.activityReimbursement.branchUnion"}, mode = SaMode.OR)
|
||||
public Result pageData(CommonPageParam pageParam) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
@@ -111,6 +119,8 @@ public class ActivityReimbursementBranchUnionController {
|
||||
cnd.andEX("YEAR(info.applyTime)", "=", pageParam.getYear());
|
||||
if (pageParam.getAudit()) {
|
||||
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.WITHDRAW.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
// 已审核列表只展示同一流程实例同一节点最新的历史任务,避免退回重提后重复显示。
|
||||
cnd.and(Exps.inSql("t.id", "SELECT MAX(latestTask.id) FROM wf_process_task latestTask WHERE latestTask.processInstanceId = t.processInstanceId AND latestTask.taskName = t.taskName AND latestTask.taskState IN (" + ProcessTaskStateEnum.FINISHED.getCode() + "," + ProcessTaskStateEnum.WITHDRAW.getCode() + "," + ProcessTaskStateEnum.INTERRUPT.getCode() + ")"));
|
||||
} else {
|
||||
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.zhgh.activity.declarereimbursement.reimbursement.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
@@ -15,6 +16,7 @@ import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.Exps;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
@@ -43,9 +45,15 @@ public class ActivityReimbursementCashierController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@SaCheckPermission("h5.activityReimbursement.cashier")
|
||||
@Ok("beetl:/platform/zhghh5/activity/declarereimbursement/reimbursement/cashier/index.html")
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("活动报销,出纳支付列表")
|
||||
@SaCheckPermission("activityReimbursement.cashier")
|
||||
@SaCheckPermission(value = {"activityReimbursement.cashier", "h5.activityReimbursement.cashier"}, mode = SaMode.OR)
|
||||
public Result pageData(CommonPageParam pageParam) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
@@ -112,6 +120,8 @@ public class ActivityReimbursementCashierController {
|
||||
cnd.andEX("YEAR(info.applyTime)", "=", pageParam.getYear());
|
||||
if (pageParam.getAudit()) {
|
||||
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.WITHDRAW.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
// 已审核列表只展示同一流程实例同一节点最新的历史任务,避免退回重提后重复显示。
|
||||
cnd.and(Exps.inSql("t.id", "SELECT MAX(latestTask.id) FROM wf_process_task latestTask WHERE latestTask.processInstanceId = t.processInstanceId AND latestTask.taskName = t.taskName AND latestTask.taskState IN (" + ProcessTaskStateEnum.FINISHED.getCode() + "," + ProcessTaskStateEnum.WITHDRAW.getCode() + "," + ProcessTaskStateEnum.INTERRUPT.getCode() + ")"));
|
||||
} else {
|
||||
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.zhgh.activity.declarereimbursement.reimbursement.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.result.Result;
|
||||
@@ -13,6 +14,7 @@ import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.Exps;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
@@ -43,6 +45,12 @@ public class ActivityReimbursementClubPrincipalController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@SaCheckPermission("h5.activityReimbursement.clubPrincipal")
|
||||
@Ok("beetl:/platform/zhghh5/activity/declarereimbursement/reimbursement/clubPrincipal/index.html")
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("activityReimbursement.clubPrincipal")
|
||||
@Ok("beetl:/platform/zhgh/activity/declarereimbursement/reimbursement/clubprincipal/form.html")
|
||||
@@ -52,7 +60,7 @@ public class ActivityReimbursementClubPrincipalController {
|
||||
|
||||
@At
|
||||
@ApiOperation("活动报销,协会负责人审核列表")
|
||||
@SaCheckPermission("activityReimbursement.clubPrincipal")
|
||||
@SaCheckPermission(value = {"activityReimbursement.clubPrincipal", "h5.activityReimbursement.clubPrincipal"}, mode = SaMode.OR)
|
||||
public Result pageData(CommonPageParam pageParam) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
@@ -111,6 +119,8 @@ public class ActivityReimbursementClubPrincipalController {
|
||||
cnd.andEX("YEAR(info.applyTime)", "=", pageParam.getYear());
|
||||
if (pageParam.getAudit()) {
|
||||
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.WITHDRAW.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
// 已审核列表只展示同一流程实例同一节点最新的历史任务,避免退回重提后重复显示。
|
||||
cnd.and(Exps.inSql("t.id", "SELECT MAX(latestTask.id) FROM wf_process_task latestTask WHERE latestTask.processInstanceId = t.processInstanceId AND latestTask.taskName = t.taskName AND latestTask.taskState IN (" + ProcessTaskStateEnum.FINISHED.getCode() + "," + ProcessTaskStateEnum.WITHDRAW.getCode() + "," + ProcessTaskStateEnum.INTERRUPT.getCode() + ")"));
|
||||
} else {
|
||||
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.zhgh.activity.declarereimbursement.reimbursement.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -22,6 +23,7 @@ import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.Exps;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
@@ -58,9 +60,15 @@ public class ActivityReimbursementDeputySchoolUnionController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@SaCheckPermission("h5.activityReimbursement.deputySchoolUnion")
|
||||
@Ok("beetl:/platform/zhghh5/activity/declarereimbursement/reimbursement/deputySchoolUnion/index.html")
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("活动报销,校工会副主席审核")
|
||||
@SaCheckPermission("activityReimbursement.deputySchoolUnion")
|
||||
@SaCheckPermission(value = {"activityReimbursement.deputySchoolUnion", "h5.activityReimbursement.deputySchoolUnion"}, mode = SaMode.OR)
|
||||
public Result pageData(CommonPageParam pageParam) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
@@ -131,6 +139,8 @@ public class ActivityReimbursementDeputySchoolUnionController {
|
||||
|
||||
if (pageParam.getAudit()) {
|
||||
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.WITHDRAW.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
// 已审核列表只展示同一流程实例同一节点最新的历史任务,避免退回重提后重复显示。
|
||||
cnd.and(Exps.inSql("t.id", "SELECT MAX(latestTask.id) FROM wf_process_task latestTask WHERE latestTask.processInstanceId = t.processInstanceId AND latestTask.taskName = t.taskName AND latestTask.taskState IN (" + ProcessTaskStateEnum.FINISHED.getCode() + "," + ProcessTaskStateEnum.WITHDRAW.getCode() + "," + ProcessTaskStateEnum.INTERRUPT.getCode() + ")"));
|
||||
} else {
|
||||
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
@@ -140,7 +150,7 @@ public class ActivityReimbursementDeputySchoolUnionController {
|
||||
} else {
|
||||
cnd.orderBy("info." + pageParam.getPageOrderName(), PageUtil.getOrder(pageParam.getPageOrderBy()));
|
||||
}
|
||||
cnd.groupBy("info.id");
|
||||
cnd.groupBy("t.id");
|
||||
sql.setCondition(cnd);
|
||||
Pagination<NutMap> pagination = activityReimbursementService.listPageMap(pageParam.getPageNumber(), pageParam.getPageSize(), sql);
|
||||
return Result.success().addData(pagination);
|
||||
@@ -148,7 +158,7 @@ public class ActivityReimbursementDeputySchoolUnionController {
|
||||
|
||||
@At
|
||||
@ApiOperation("预算/报销汇总表")
|
||||
@SaCheckPermission("activityReimbursement.deputySchoolUnion")
|
||||
@SaCheckPermission(value = {"activityReimbursement.deputySchoolUnion", "h5.activityReimbursement.deputySchoolUnion"}, mode = SaMode.OR)
|
||||
public Result budgetSummaryData(CommonPageParam pageParam) {
|
||||
if (pageParam.getYear() == null) {
|
||||
return Result.error("请选择年度");
|
||||
@@ -168,7 +178,7 @@ public class ActivityReimbursementDeputySchoolUnionController {
|
||||
|
||||
@At
|
||||
@SLog(tag = "报销", msg = "校工会副主席审核了一条记录")
|
||||
@SaCheckPermission("activityReimbursement.deputySchoolUnion")
|
||||
@SaCheckPermission(value = {"activityReimbursement.deputySchoolUnion", "h5.activityReimbursement.deputySchoolUnion"}, mode = SaMode.OR)
|
||||
public Result doReview(@Param("data") String param, String id) {
|
||||
Dict args = Json.fromJson(Dict.class, param);
|
||||
if (args.getInt("submitType") == 1) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.activity.declarereimbursement.reimbursement.controlle
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.hutool.core.convert.NumberChineseFormatter;
|
||||
import cn.hutool.core.convert.NumberWordFormatter;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
@@ -89,9 +90,15 @@ public class ActivityReimbursementMineController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@SaCheckPermission("h5.activityReimbursement.mine")
|
||||
@Ok("beetl:/platform/zhghh5/activity/declarereimbursement/reimbursement/mine/index.html")
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("活动报销,我的申请列表")
|
||||
@SaCheckPermission("activityReimbursement.mine")
|
||||
@SaCheckPermission(value = {"activityReimbursement.mine", "h5.activityReimbursement.mine"}, mode = SaMode.OR)
|
||||
public Result pageData(CommonPageParam pageParam) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
@@ -215,7 +222,7 @@ public class ActivityReimbursementMineController {
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("activityReimbursement.mine")
|
||||
@SaCheckPermission(value = {"activityReimbursement.mine", "h5.activityReimbursement.mine"}, mode = SaMode.OR)
|
||||
@SLog(tag = "活动报销", msg = "导出报销凭证")
|
||||
public void doExportReimbursement(@Valid String id, HttpServletResponse response) {
|
||||
HashMap<String, Object> docData = new HashMap<>();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.zhgh.activity.declarereimbursement.reimbursement.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
@@ -15,6 +16,7 @@ import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.Exps;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
@@ -45,9 +47,15 @@ public class ActivityReimbursementSchoolFinanceController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@SaCheckPermission("h5.activityReimbursement.schoolFinance")
|
||||
@Ok("beetl:/platform/zhghh5/activity/declarereimbursement/reimbursement/schoolFinance/index.html")
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("活动报销,校工会财务审核")
|
||||
@SaCheckPermission("activityReimbursement.schoolFinance")
|
||||
@SaCheckPermission(value = {"activityReimbursement.schoolFinance", "h5.activityReimbursement.schoolFinance"}, mode = SaMode.OR)
|
||||
public Result pageData(CommonPageParam pageParam) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
@@ -114,6 +122,8 @@ public class ActivityReimbursementSchoolFinanceController {
|
||||
cnd.andEX("YEAR(info.applyTime)", "=", pageParam.getYear());
|
||||
if (pageParam.getAudit()) {
|
||||
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.WITHDRAW.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
// 已审核列表只展示同一流程实例同一节点最新的历史任务,避免退回重提后重复显示。
|
||||
cnd.and(Exps.inSql("t.id", "SELECT MAX(latestTask.id) FROM wf_process_task latestTask WHERE latestTask.processInstanceId = t.processInstanceId AND latestTask.taskName = t.taskName AND latestTask.taskState IN (" + ProcessTaskStateEnum.FINISHED.getCode() + "," + ProcessTaskStateEnum.WITHDRAW.getCode() + "," + ProcessTaskStateEnum.INTERRUPT.getCode() + ")"));
|
||||
} else {
|
||||
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.zhgh.activity.declarereimbursement.reimbursement.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
@@ -15,6 +16,7 @@ import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.Exps;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
@@ -46,6 +48,12 @@ public class ActivityReimbursementSchoolPrincipalController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@SaCheckPermission("h5.activityReimbursement.schoolPrincipal")
|
||||
@Ok("beetl:/platform/zhghh5/activity/declarereimbursement/reimbursement/schoolPrincipal/index.html")
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("activityReimbursement.schoolPrincipal")
|
||||
@Ok("beetl:/platform/zhgh/activity/declarereimbursement/reimbursement/schoolprincipal/form.html")
|
||||
@@ -55,7 +63,7 @@ public class ActivityReimbursementSchoolPrincipalController {
|
||||
|
||||
@At
|
||||
@ApiOperation("活动报销,校工会负责人审核列表")
|
||||
@SaCheckPermission("activityReimbursement.schoolPrincipal")
|
||||
@SaCheckPermission(value = {"activityReimbursement.schoolPrincipal", "h5.activityReimbursement.schoolPrincipal"}, mode = SaMode.OR)
|
||||
public Result pageData(CommonPageParam pageParam) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
@@ -123,6 +131,8 @@ public class ActivityReimbursementSchoolPrincipalController {
|
||||
cnd.andEX("YEAR(info.applyTime)", "=", pageParam.getYear());
|
||||
if (pageParam.getAudit()) {
|
||||
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.WITHDRAW.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
// 已审核列表只展示同一流程实例同一节点最新的历史任务,避免退回重提后重复显示。
|
||||
cnd.and(Exps.inSql("t.id", "SELECT MAX(latestTask.id) FROM wf_process_task latestTask WHERE latestTask.processInstanceId = t.processInstanceId AND latestTask.taskName = t.taskName AND latestTask.taskState IN (" + ProcessTaskStateEnum.FINISHED.getCode() + "," + ProcessTaskStateEnum.WITHDRAW.getCode() + "," + ProcessTaskStateEnum.INTERRUPT.getCode() + ")"));
|
||||
} else {
|
||||
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.Exps;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
@@ -61,6 +62,12 @@ public class ActivityReimbursementSchoolUnionController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@SaCheckPermission("h5.activityReimbursement.schoolUnion")
|
||||
@Ok("beetl:/platform/zhghh5/activity/declarereimbursement/reimbursement/schoolUnion/index.html")
|
||||
public void h5Index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("activityReimbursement.schoolUnion")
|
||||
@Ok("beetl:/platform/zhgh/activity/declarereimbursement/reimbursement/schoolunion/form.html")
|
||||
@@ -70,7 +77,7 @@ public class ActivityReimbursementSchoolUnionController {
|
||||
|
||||
@At
|
||||
@ApiOperation("活动报销,校工会审核列表")
|
||||
@SaCheckPermission("activityReimbursement.schoolUnion")
|
||||
@SaCheckPermission(value = {"activityReimbursement.schoolUnion", "h5.activityReimbursement.schoolUnion"}, mode = SaMode.OR)
|
||||
public Result pageData(CommonPageParam pageParam) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
@@ -139,6 +146,8 @@ public class ActivityReimbursementSchoolUnionController {
|
||||
|
||||
if (pageParam.getAudit()) {
|
||||
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.WITHDRAW.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
// 已审核列表只展示同一流程实例同一节点最新的历史任务,避免退回重提后重复显示。
|
||||
cnd.and(Exps.inSql("t.id", "SELECT MAX(latestTask.id) FROM wf_process_task latestTask WHERE latestTask.processInstanceId = t.processInstanceId AND latestTask.taskName = t.taskName AND latestTask.taskState IN (" + ProcessTaskStateEnum.FINISHED.getCode() + "," + ProcessTaskStateEnum.WITHDRAW.getCode() + "," + ProcessTaskStateEnum.INTERRUPT.getCode() + ")"));
|
||||
} else {
|
||||
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
@@ -157,7 +166,7 @@ public class ActivityReimbursementSchoolUnionController {
|
||||
|
||||
@At
|
||||
@SLog(tag = "报销", msg = "校工会审核了一条记录")
|
||||
@SaCheckPermission("activityReimbursement.schoolUnion")
|
||||
@SaCheckPermission(value = {"activityReimbursement.schoolUnion", "h5.activityReimbursement.schoolUnion"}, mode = SaMode.OR)
|
||||
public Result doReview(@Param("data") String param, String id) {
|
||||
activityReimbursementSchoolUnionService.doReview(param, id);
|
||||
return Result.success();
|
||||
|
||||
@@ -1,31 +1,17 @@
|
||||
package com.budwk.app.zhgh.dayofficework.buildHome.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.base.utils.SysOfficeTemplateUtil;
|
||||
import com.budwk.app.sys.models.Sys_union;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.buildHome.models.BuildHomeLittleHouse;
|
||||
import com.budwk.app.zhgh.dayofficework.buildHome.param.BuildHomeLittleHousePageForm;
|
||||
import com.budwk.app.zhgh.dayofficework.buildHome.service.BuildHomeLittleHouseService;
|
||||
import com.deepoove.poi.XWPFTemplate;
|
||||
import com.deepoove.poi.config.Configure;
|
||||
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
|
||||
import io.swagger.annotations.Api;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
@@ -34,12 +20,8 @@ import org.nutz.mvc.annotation.Ok;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/buildHome/littleHouse")
|
||||
@@ -48,10 +30,9 @@ import java.util.stream.IntStream;
|
||||
@Api(value = "建家小家建设情况")
|
||||
public class BuildHomeLittleHouseController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
@Inject("refer:buildHomeLittleHouseServiceImpl")
|
||||
private BuildHomeLittleHouseService buildHomeLittleHouseService;
|
||||
|
||||
@Inject
|
||||
private SysOfficeTemplateUtil sysOfficeTemplateUtil;
|
||||
|
||||
@@ -65,41 +46,47 @@ public class BuildHomeLittleHouseController {
|
||||
@At
|
||||
@SaCheckPermission("buildHome.littleHouse")
|
||||
public Result pageData(BuildHomeLittleHousePageForm pageForm) {
|
||||
Sql sql = Sqls.create("select * from build_home_little_house $condition");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("unionId", "=", SecurityUtil.getUnionId());
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
cnd.where().andLike("unitName", pageForm.getSearchKeyword());
|
||||
}
|
||||
cnd.asc("sortNum");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
return Result.success(buildHomeLittleHouseService.pageData(pageForm));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("buildHome.littleHouse")
|
||||
public Result currentUserOrg() {
|
||||
return Result.success(buildHomeLittleHouseService.currentUserOrg());
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("buildHome.littleHouse")
|
||||
public Result save(BuildHomeLittleHouse house) {
|
||||
if (house.getId() == null) {
|
||||
house.setUnionId(SecurityUtil.getUnionId());
|
||||
house.setUnionName(SecurityUtil.getUnionId());
|
||||
}
|
||||
dao.insertOrUpdate(house);
|
||||
buildHomeLittleHouseService.saveHouse(house);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At("/delete/?")
|
||||
@SaCheckPermission("buildHome.littleHouse")
|
||||
public Result delete(@Valid Long id) {
|
||||
dao.delete(BuildHomeLittleHouse.class, id);
|
||||
buildHomeLittleHouseService.delete(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At("/detail/?")
|
||||
@SaCheckPermission("buildHome.littleHouse")
|
||||
public Result detail(@Valid Long id) {
|
||||
BuildHomeLittleHouse house = dao.fetch(BuildHomeLittleHouse.class, id);
|
||||
return Result.success(house);
|
||||
return Result.success(buildHomeLittleHouseService.fetch(id));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("buildHome.littleHouse")
|
||||
public Result saveCoordinate(Long id, Double mapX, Double mapY) {
|
||||
buildHomeLittleHouseService.saveCoordinate(id, mapX, mapY);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("buildHome.littleHouse")
|
||||
public Result clearCoordinate(Long id) {
|
||||
buildHomeLittleHouseService.clearCoordinate(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At("/exportXlsx")
|
||||
@@ -107,23 +94,7 @@ public class BuildHomeLittleHouseController {
|
||||
@Ok("void")
|
||||
public void exportXlsx(HttpServletResponse response) {
|
||||
try {
|
||||
String unionId = SecurityUtil.getUnionId();
|
||||
Cnd cnd = Cnd.where("unionId", "=", unionId);
|
||||
|
||||
Sys_union union = dao.fetch(Sys_union.class, unionId);
|
||||
if (union == null) {
|
||||
throw new BaseException("未找到ID为 " + unionId + " 的工会信息");
|
||||
}
|
||||
|
||||
// 准备文档数据Map
|
||||
Map<String, Object> docMap = new HashMap<>();
|
||||
docMap.put("unionName", union.getName()); // 工会名称
|
||||
docMap.put("date", DateUtil.today()); // 当前日期
|
||||
|
||||
// 查询小家建设数据并设置序号
|
||||
List<BuildHomeLittleHouse> list = dao.query(BuildHomeLittleHouse.class, cnd);
|
||||
IntStream.range(0, list.size()).forEach(i -> list.get(i).setIndex(i + 1)); // 为每条数据设置序号
|
||||
docMap.put("list", list); // 将数据列表放入Map
|
||||
Map<String, Object> docMap = buildHomeLittleHouseService.buildExportData();
|
||||
|
||||
// 配置表格渲染策略
|
||||
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.budwk.app.zhgh.dayofficework.buildHome.models;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("build_home_little_house")
|
||||
@@ -22,6 +25,11 @@ public class BuildHomeLittleHouse extends BaseModel {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String unitName;
|
||||
|
||||
@Column
|
||||
@Comment("单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String unitId;
|
||||
|
||||
@Column
|
||||
@Comment("分工会ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@@ -52,6 +60,21 @@ public class BuildHomeLittleHouse extends BaseModel {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String openTime;
|
||||
|
||||
@Column
|
||||
@Comment("图片视频资料")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List<JSONObject> mediaFiles;
|
||||
|
||||
@Column
|
||||
@Comment("地图X坐标百分比")
|
||||
@ColDefine(type = ColType.FLOAT, width = 8, precision = 4)
|
||||
private Double mapX;
|
||||
|
||||
@Column
|
||||
@Comment("地图Y坐标百分比")
|
||||
@ColDefine(type = ColType.FLOAT, width = 8, precision = 4)
|
||||
private Double mapY;
|
||||
|
||||
@Column
|
||||
@Comment("排序")
|
||||
@ColDefine(type = ColType.INT)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.budwk.app.zhgh.dayofficework.buildHome.service;
|
||||
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.buildHome.models.BuildHomeLittleHouse;
|
||||
import com.budwk.app.zhgh.dayofficework.buildHome.param.BuildHomeLittleHousePageForm;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface BuildHomeLittleHouseService extends BaseService<BuildHomeLittleHouse> {
|
||||
|
||||
/**
|
||||
* 分页查询小家建设数据,系统管理员查看全部,其他用户查看本分工会。
|
||||
*
|
||||
* @param pageForm 查询分页参数。
|
||||
* @return 小家建设分页数据。
|
||||
*/
|
||||
Pagination pageData(BuildHomeLittleHousePageForm pageForm);
|
||||
|
||||
/**
|
||||
* 获取当前登录用户所属工会和单位,用于新增小家记录时自动带出组织信息。
|
||||
*
|
||||
* @return 当前用户组织信息。
|
||||
*/
|
||||
Map<String, String> currentUserOrg();
|
||||
|
||||
/**
|
||||
* 保存小家建设记录,新增时写入当前用户组织,编辑时保留原组织归属。
|
||||
*
|
||||
* @param house 小家建设记录。
|
||||
*/
|
||||
void saveHouse(BuildHomeLittleHouse house);
|
||||
|
||||
/**
|
||||
* 保存小家地图坐标,仅允许系统管理员操作。
|
||||
*
|
||||
* @param id 小家记录ID。
|
||||
* @param mapX 地图X坐标百分比。
|
||||
* @param mapY 地图Y坐标百分比。
|
||||
*/
|
||||
void saveCoordinate(Long id, Double mapX, Double mapY);
|
||||
|
||||
/**
|
||||
* 清空小家地图坐标,仅允许系统管理员操作。
|
||||
*
|
||||
* @param id 小家记录ID。
|
||||
*/
|
||||
void clearCoordinate(Long id);
|
||||
|
||||
/**
|
||||
* 准备小家建设汇总导出数据。
|
||||
*
|
||||
* @return 模板渲染数据。
|
||||
*/
|
||||
Map<String, Object> buildExportData();
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.budwk.app.zhgh.dayofficework.buildHome.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.sys.models.Sys_union;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.buildHome.models.BuildHomeLittleHouse;
|
||||
import com.budwk.app.zhgh.dayofficework.buildHome.param.BuildHomeLittleHousePageForm;
|
||||
import com.budwk.app.zhgh.dayofficework.buildHome.service.BuildHomeLittleHouseService;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class BuildHomeLittleHouseServiceImpl extends BaseServiceImpl<BuildHomeLittleHouse> implements BuildHomeLittleHouseService {
|
||||
|
||||
public BuildHomeLittleHouseServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pagination pageData(BuildHomeLittleHousePageForm pageForm) {
|
||||
Sql sql = Sqls.create("select * from build_home_little_house $condition");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (!isSysAdmin()) {
|
||||
cnd.and("unionId", "=", SecurityUtil.getUnionId());
|
||||
}
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup searchGroup = new SqlExpressionGroup();
|
||||
searchGroup.orLike("unitName", pageForm.getSearchKeyword());
|
||||
searchGroup.orLike("unionName", pageForm.getSearchKeyword());
|
||||
cnd.where().and(searchGroup);
|
||||
}
|
||||
cnd.asc("sortNum");
|
||||
sql.setCondition(cnd);
|
||||
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> currentUserOrg() {
|
||||
Map<String, String> currentUserOrg = new HashMap<>();
|
||||
View_user user = dao().fetch(View_user.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
|
||||
|
||||
String unionId = user != null && StrUtil.isNotBlank(user.getUnionId()) ? user.getUnionId() : SecurityUtil.getUnionId();
|
||||
String unionName = user != null ? user.getUnionName() : null;
|
||||
if (StrUtil.isBlank(unionName) && StrUtil.isNotBlank(unionId)) {
|
||||
Sys_union union = dao().fetch(Sys_union.class, unionId);
|
||||
unionName = union != null ? union.getName() : unionId;
|
||||
}
|
||||
|
||||
currentUserOrg.put("unionId", unionId);
|
||||
currentUserOrg.put("unionName", unionName);
|
||||
currentUserOrg.put("unitId", user != null ? user.getUnitId() : SecurityUtil.getUnitId());
|
||||
currentUserOrg.put("unitName", user != null ? user.getUnitName() : null);
|
||||
return currentUserOrg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveHouse(BuildHomeLittleHouse house) {
|
||||
if (house.getId() == null) {
|
||||
fillCurrentUserOrg(house);
|
||||
} else {
|
||||
keepOriginalOrg(house);
|
||||
}
|
||||
dao().insertOrUpdate(house);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveCoordinate(Long id, Double mapX, Double mapY) {
|
||||
if (!isSysAdmin()) {
|
||||
throw new BaseException("仅系统管理员可以采集坐标");
|
||||
}
|
||||
BuildHomeLittleHouse house = fetchRequiredHouse(id);
|
||||
if (!isValidCoordinate(mapX) || !isValidCoordinate(mapY)) {
|
||||
throw new BaseException("坐标范围必须在0到100之间");
|
||||
}
|
||||
dao().update(BuildHomeLittleHouse.class, Chain.make("mapX", mapX).add("mapY", mapY), Cnd.where("id", "=", house.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCoordinate(Long id) {
|
||||
if (!isSysAdmin()) {
|
||||
throw new BaseException("仅系统管理员可以清空坐标");
|
||||
}
|
||||
BuildHomeLittleHouse house = fetchRequiredHouse(id);
|
||||
dao().update(BuildHomeLittleHouse.class, Chain.make("mapX", null).add("mapY", null), Cnd.where("id", "=", house.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> buildExportData() {
|
||||
boolean sysAdmin = isSysAdmin();
|
||||
String unionId = SecurityUtil.getUnionId();
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (!sysAdmin) {
|
||||
cnd.and("unionId", "=", unionId);
|
||||
}
|
||||
cnd.asc("sortNum");
|
||||
|
||||
Sys_union union = dao().fetch(Sys_union.class, unionId);
|
||||
if (!sysAdmin && union == null) {
|
||||
throw new BaseException("未找到ID为 " + unionId + " 的工会信息");
|
||||
}
|
||||
|
||||
List<BuildHomeLittleHouse> list = query(cnd);
|
||||
IntStream.range(0, list.size()).forEach(i -> list.get(i).setIndex(i + 1));
|
||||
|
||||
Map<String, Object> docMap = new HashMap<>();
|
||||
docMap.put("unionName", sysAdmin ? "全部工会" : union.getName());
|
||||
docMap.put("date", DateUtil.today());
|
||||
docMap.put("list", list);
|
||||
return docMap;
|
||||
}
|
||||
|
||||
private void fillCurrentUserOrg(BuildHomeLittleHouse house) {
|
||||
Map<String, String> currentUserOrg = currentUserOrg();
|
||||
house.setUnionId(currentUserOrg.get("unionId"));
|
||||
house.setUnionName(currentUserOrg.get("unionName"));
|
||||
house.setUnitId(currentUserOrg.get("unitId"));
|
||||
house.setUnitName(currentUserOrg.get("unitName"));
|
||||
}
|
||||
|
||||
private void keepOriginalOrg(BuildHomeLittleHouse house) {
|
||||
BuildHomeLittleHouse oldHouse = dao().fetch(BuildHomeLittleHouse.class, house.getId());
|
||||
if (oldHouse == null) {
|
||||
fillCurrentUserOrg(house);
|
||||
return;
|
||||
}
|
||||
house.setUnionId(oldHouse.getUnionId());
|
||||
house.setUnionName(oldHouse.getUnionName());
|
||||
house.setUnitId(oldHouse.getUnitId());
|
||||
house.setUnitName(oldHouse.getUnitName());
|
||||
}
|
||||
|
||||
private BuildHomeLittleHouse fetchRequiredHouse(Long id) {
|
||||
if (id == null) {
|
||||
throw new BaseException("小家记录ID不能为空");
|
||||
}
|
||||
BuildHomeLittleHouse house = dao().fetch(BuildHomeLittleHouse.class, id);
|
||||
if (house == null) {
|
||||
throw new BaseException("未找到小家记录");
|
||||
}
|
||||
return house;
|
||||
}
|
||||
|
||||
private boolean isSysAdmin() {
|
||||
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name());
|
||||
}
|
||||
|
||||
private boolean isValidCoordinate(Double value) {
|
||||
return value != null && value >= 0 && value <= 100;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.budwk.app.zhgh.dayofficework.caredata;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.zhgh.dayofficework.caredata.service.CareDataLeaderService;
|
||||
import io.swagger.annotations.Api;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/careData/leader")
|
||||
@Api("领导驾驶舱")
|
||||
@Ok("json:full")
|
||||
public class CareDataLeaderCon {
|
||||
|
||||
@Inject("refer:careDataLeaderServiceImpl")
|
||||
private CareDataLeaderService careDataLeaderService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/careData/leader/indexOverlay.html")
|
||||
@SaCheckPermission("careData.union")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("proposal")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/careData/leader/proposalOverlay.html")
|
||||
@SaCheckPermission("careData.union")
|
||||
public void proposal() {
|
||||
}
|
||||
|
||||
@At("condolence")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/careData/leader/condolenceOverlay.html")
|
||||
@SaCheckPermission("careData.union")
|
||||
public void condolence() {
|
||||
}
|
||||
|
||||
@At("baseUnion")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/careData/leader/baseUnionOverlay.html")
|
||||
@SaCheckPermission("careData.union")
|
||||
public void baseUnion() {
|
||||
}
|
||||
|
||||
@At("club")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/careData/leader/clubOverlay.html")
|
||||
@SaCheckPermission("careData.union")
|
||||
public void club() {
|
||||
}
|
||||
|
||||
@At("dataMetric")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/careData/leader/dataMetricOverlay.html")
|
||||
@SaCheckPermission("careData.union")
|
||||
public void dataMetric() {
|
||||
}
|
||||
|
||||
@At("assetData")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/careData/leader/assetDataOverlay.html")
|
||||
@SaCheckPermission("careData.union")
|
||||
public void assetData() {
|
||||
}
|
||||
|
||||
@At("staffHome")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/careData/leader/staffHomeOverlay.html")
|
||||
@SaCheckPermission("careData.union")
|
||||
public void staffHome() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("careData.union")
|
||||
public Result proposalData(String sessionId) {
|
||||
return Result.success(careDataLeaderService.proposalData(sessionId));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("careData.union")
|
||||
public Result condolenceData(Integer year) {
|
||||
return Result.success(careDataLeaderService.condolenceData(year));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("careData.union")
|
||||
public Result baseUnionData() {
|
||||
return Result.success(careDataLeaderService.baseUnionData());
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("careData.union")
|
||||
public Result clubData() {
|
||||
return Result.success(careDataLeaderService.clubData());
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("careData.union")
|
||||
public Result dataMetricData(Integer year) {
|
||||
return Result.success(careDataLeaderService.dataMetricData(year));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("careData.union")
|
||||
public Result assetDataData() {
|
||||
return Result.success(careDataLeaderService.assetDataData());
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("careData.union")
|
||||
public Result staffHomeData() {
|
||||
return Result.success(careDataLeaderService.staffHomeData());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.budwk.app.zhgh.dayofficework.caredata.service;
|
||||
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
/**
|
||||
* 领导驾驶舱数据服务。
|
||||
*/
|
||||
public interface CareDataLeaderService {
|
||||
|
||||
/**
|
||||
* 查询提案概览、办结和满意率数据。
|
||||
*
|
||||
* @param sessionId 教代会届次ID,为空时取最新届次。
|
||||
* @return 提案概览数据,包含 session、overview、caseResults。
|
||||
*/
|
||||
NutMap proposalData(String sessionId);
|
||||
|
||||
/**
|
||||
* 查询慰问类型排行数据。
|
||||
*
|
||||
* @param year 年份,为空时取当前年份。
|
||||
* @return 慰问排行数据。
|
||||
*/
|
||||
NutMap condolenceData(Integer year);
|
||||
|
||||
/**
|
||||
* 查询分工会会员数排行。
|
||||
*
|
||||
* @return 分工会会员统计数据。
|
||||
*/
|
||||
NutMap baseUnionData();
|
||||
|
||||
/**
|
||||
* 查询协会人数排行。
|
||||
*
|
||||
* @return 协会人员统计数据。
|
||||
*/
|
||||
NutMap clubData();
|
||||
|
||||
/**
|
||||
* 查询驾驶舱中部指标数据。
|
||||
*
|
||||
* @param year 年份,为空时取当前年份。
|
||||
* @return 预算、疗休养、荣誉、困难帮扶和报销统计数据。
|
||||
*/
|
||||
NutMap dataMetricData(Integer year);
|
||||
|
||||
/**
|
||||
* 查询资产使用状态分布。
|
||||
*
|
||||
* @return 资产使用状态统计数据。
|
||||
*/
|
||||
NutMap assetDataData();
|
||||
|
||||
/**
|
||||
* 查询职工小家地图点位数据。
|
||||
*
|
||||
* @return 职工小家列表数据。
|
||||
*/
|
||||
NutMap staffHomeData();
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
package com.budwk.app.zhgh.dayofficework.caredata.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.caredata.service.CareDataLeaderService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class CareDataLeaderServiceImpl implements CareDataLeaderService {
|
||||
|
||||
private static final int FINISHED_STATE = 20;
|
||||
|
||||
private final Dao dao;
|
||||
|
||||
public CareDataLeaderServiceImpl(Dao dao) {
|
||||
this.dao = dao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NutMap proposalData(String sessionId) {
|
||||
String currentSessionId = StrUtil.blankToDefault(sessionId, latestSessionId());
|
||||
if (StrUtil.isBlank(currentSessionId)) {
|
||||
return NutMap.NEW();
|
||||
}
|
||||
|
||||
NutMap session = sessionInfo(currentSessionId);
|
||||
List<NutMap> caseResults = caseResultData(currentSessionId);
|
||||
NutMap overview = overviewData(currentSessionId, caseResults);
|
||||
|
||||
return NutMap.NEW()
|
||||
.addv("session", session)
|
||||
.addv("overview", overview)
|
||||
.addv("caseResults", caseResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NutMap condolenceData(Integer year) {
|
||||
int targetYear = year == null ? LocalDate.now().getYear() : year;
|
||||
List<NutMap> rows = condolenceTypeRows(targetYear);
|
||||
long total = rows.stream().mapToLong(row -> row.getLong("count", 0L)).sum();
|
||||
List<NutMap> sortedRows = new ArrayList<>(rows);
|
||||
sortedRows.sort((left, right) -> Long.compare(right.getLong("count", 0L), left.getLong("count", 0L)));
|
||||
List<NutMap> topRows = new ArrayList<>();
|
||||
int index = 1;
|
||||
for (NutMap row : sortedRows) {
|
||||
if (index > 5) {
|
||||
break;
|
||||
}
|
||||
long count = row.getLong("count", 0L);
|
||||
topRows.add(NutMap.NEW()
|
||||
.addv("index", index++)
|
||||
.addv("typeId", row.getString("id", ""))
|
||||
.addv("typeCode", row.getString("code", ""))
|
||||
.addv("name", row.getString("name", ""))
|
||||
.addv("count", count)
|
||||
.addv("rate", percent(count, total))
|
||||
.addv("rateValue", percentValue(count, total)));
|
||||
}
|
||||
|
||||
return NutMap.NEW()
|
||||
.addv("year", targetYear)
|
||||
.addv("total", total)
|
||||
.addv("types", topRows);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NutMap baseUnionData() {
|
||||
return NutMap.NEW().addv("unions", baseUnionRows());
|
||||
}
|
||||
|
||||
@Override
|
||||
public NutMap clubData() {
|
||||
return NutMap.NEW().addv("clubs", clubRows());
|
||||
}
|
||||
|
||||
@Override
|
||||
public NutMap dataMetricData(Integer year) {
|
||||
int targetYear = year == null ? LocalDate.now().getYear() : year;
|
||||
return NutMap.NEW()
|
||||
.addv("year", targetYear)
|
||||
.addv("budgetTotal", schoolBudgetTotal(targetYear))
|
||||
.addv("tourCount", tourCount(targetYear))
|
||||
.addv("honorCount", honorCount(targetYear))
|
||||
.addv("difficultCount", difficultCount(targetYear))
|
||||
.addv("reimburseTotal", reimburseTotal(targetYear));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NutMap assetDataData() {
|
||||
List<NutMap> states = assetUsageStateRows();
|
||||
long total = states.stream().mapToLong(row -> row.getLong("value", 0L)).sum();
|
||||
return NutMap.NEW()
|
||||
.addv("total", total)
|
||||
.addv("states", states);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NutMap staffHomeData() {
|
||||
return NutMap.NEW().addv("houses", littleHouseRows());
|
||||
}
|
||||
|
||||
private List<NutMap> littleHouseRows() {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
id,
|
||||
unionName,
|
||||
unitName,
|
||||
address,
|
||||
mediaFiles,
|
||||
mapX,
|
||||
mapY,
|
||||
sortNum
|
||||
FROM build_home_little_house
|
||||
WHERE mapX IS NOT NULL
|
||||
AND mapY IS NOT NULL
|
||||
$unionFilter
|
||||
ORDER BY sortNum ASC, id ASC
|
||||
""");
|
||||
setUnionFilter(sql, "unionId", null);
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
private String latestSessionId() {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT id
|
||||
FROM teacher_congress_session
|
||||
ORDER BY startDate DESC
|
||||
LIMIT 1
|
||||
""");
|
||||
return firstMap(sql).getString("id", "");
|
||||
}
|
||||
|
||||
private List<NutMap> condolenceTypeRows(int year) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
t.id,
|
||||
t.code,
|
||||
t.name,
|
||||
COUNT(info.id) AS count
|
||||
FROM condolence_type t
|
||||
LEFT JOIN condolence info ON info.type = t.id
|
||||
AND YEAR(info.createTime) = @year
|
||||
$unionFilter
|
||||
WHERE t.enable = 1
|
||||
GROUP BY t.id, t.code, t.name, t.sortNum
|
||||
ORDER BY count DESC, t.sortNum ASC, t.code ASC
|
||||
""");
|
||||
sql.setParam("year", year);
|
||||
setUnionFilter(sql, "info.applyUnionId", null);
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
private List<NutMap> baseUnionRows() {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
un.id,
|
||||
un.name AS unionname,
|
||||
un.unionCode,
|
||||
(
|
||||
SELECT COUNT(1)
|
||||
FROM vw_user u
|
||||
WHERE u.member = 1
|
||||
AND u.unionId = un.id
|
||||
) AS value
|
||||
FROM sys_union un
|
||||
WHERE un.delFlag = 0
|
||||
$unionFilter
|
||||
ORDER BY un.unionCode
|
||||
""");
|
||||
setUnionFilter(sql, "un.id", null);
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
private List<NutMap> clubRows() {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
c.id,
|
||||
c.clubName,
|
||||
c.clubCode,
|
||||
(
|
||||
SELECT COUNT(1)
|
||||
FROM club_user cu
|
||||
WHERE cu.clubId = c.id
|
||||
AND cu.delFlag = 0
|
||||
) AS value
|
||||
FROM sys_club c
|
||||
WHERE c.delFlag = 0
|
||||
AND c.dismiss = 0
|
||||
ORDER BY c.clubCode
|
||||
""");
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
private List<NutMap> assetUsageStateRows() {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
IFNULL(NULLIF(TRIM(t1.assetUsageStateName), ''), @emptyName) AS name,
|
||||
SUM(IFNULL(t1.assetQuantity, 0)) AS value
|
||||
FROM `asset` t1
|
||||
WHERE 1 = 1
|
||||
$unionFilter
|
||||
GROUP BY IFNULL(NULLIF(TRIM(t1.assetUsageStateName), ''), @emptyName)
|
||||
ORDER BY value DESC, name ASC
|
||||
""");
|
||||
sql.setParam("emptyName", "未填写");
|
||||
setUnionFilter(sql, "t1.assetUseUnionId", null);
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
private BigDecimal schoolBudgetTotal(int year) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT IFNULL(SUM(totalQuota), 0) AS total
|
||||
FROM outlay_manage_school
|
||||
WHERE delFlag = 0
|
||||
AND `year` = @year
|
||||
""");
|
||||
sql.setParam("year", year);
|
||||
return decimalValue(firstMap(sql), "total");
|
||||
}
|
||||
|
||||
private long tourCount(int year) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT COUNT(DISTINCT info.id) AS count
|
||||
FROM recuperation_enroll info
|
||||
WHERE info.isNormal = 1
|
||||
AND info.stateId NOT IN (2715, 2725, 2735)
|
||||
AND YEAR(info.signingUptime) = @year
|
||||
$unionFilter
|
||||
""");
|
||||
// 当前项目没有参考项目的 tour_ledger 表,疗休养人数改用现有报名表统计正常且未被驳回的数据。
|
||||
sql.setParam("year", year);
|
||||
setUnionFilter(sql, "info.takePartInUnionId", null);
|
||||
return firstMap(sql).getLong("count", 0L);
|
||||
}
|
||||
|
||||
private long honorCount(int year) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT COUNT(h.id) AS count
|
||||
FROM honor h
|
||||
WHERE h.delFlag = 0
|
||||
AND YEAR(h.grantDate) = @year
|
||||
$unionFilter
|
||||
""");
|
||||
sql.setParam("year", year);
|
||||
setUnionFilter(sql, "h.unionId", null);
|
||||
return firstMap(sql).getLong("count", 0L);
|
||||
}
|
||||
|
||||
private long difficultCount(int year) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT COUNT(DISTINCT info.id) AS count
|
||||
FROM difficult_help_info info
|
||||
INNER JOIN wf_process_instance ins ON ins.businessNo = info.id
|
||||
WHERE info.delFlag = 0
|
||||
AND ins.state = @finished
|
||||
AND YEAR(info.applyTime) = @year
|
||||
$unionFilter
|
||||
""");
|
||||
sql.setParam("year", year);
|
||||
sql.setParam("finished", FINISHED_STATE);
|
||||
setUnionFilter(sql, "info.unionId", null);
|
||||
return firstMap(sql).getLong("count", 0L);
|
||||
}
|
||||
|
||||
private BigDecimal reimburseTotal(int year) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT IFNULL(SUM(IFNULL(info.money, 0)), 0) AS total
|
||||
FROM union_reimburse info
|
||||
WHERE YEAR(info.createTime) = @year
|
||||
$unionFilter
|
||||
""");
|
||||
// 当前项目 union_reimburse 没有参考项目的 realMoney、condolenceMoney、stateId 字段,金额按现有 money 字段统计。
|
||||
sql.setParam("year", year);
|
||||
setUnionFilter(sql, "info.unionId", null);
|
||||
return decimalValue(firstMap(sql), "total");
|
||||
}
|
||||
|
||||
private NutMap sessionInfo(String sessionId) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT id, fullName
|
||||
FROM teacher_congress_session
|
||||
WHERE id = @sessionId
|
||||
""");
|
||||
sql.setParam("sessionId", sessionId);
|
||||
return firstMap(sql);
|
||||
}
|
||||
|
||||
private NutMap overviewData(String sessionId, List<NutMap> caseResults) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
COUNT(info.id) AS total,
|
||||
SUM(CASE WHEN ins.state = @finished THEN 1 ELSE 0 END) AS doneCount
|
||||
FROM proposal_info info
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
|
||||
WHERE info.sessionId = @sessionId
|
||||
""");
|
||||
sql.setParam("sessionId", sessionId);
|
||||
sql.setParam("finished", FINISHED_STATE);
|
||||
NutMap overview = firstMap(sql);
|
||||
|
||||
long filedCount = 0L;
|
||||
long rejectedCount = 0L;
|
||||
long suggestionCount = 0L;
|
||||
for (NutMap item : caseResults) {
|
||||
String name = item.getString("name", "");
|
||||
long count = item.getLong("count", 0L);
|
||||
if (containsAny(name, "不予", "不立案")) {
|
||||
rejectedCount += count;
|
||||
} else if (containsAny(name, "意见", "建议")) {
|
||||
suggestionCount += count;
|
||||
} else if (name.contains("立案")) {
|
||||
filedCount += count;
|
||||
}
|
||||
}
|
||||
|
||||
long total = overview.getLong("total", 0L);
|
||||
long doneCount = overview.getLong("doneCount", 0L);
|
||||
long satisfiedCount = satisfiedCount(sessionId);
|
||||
|
||||
return NutMap.NEW()
|
||||
.addv("total", total)
|
||||
.addv("filedCount", filedCount)
|
||||
.addv("suggestionCount", suggestionCount)
|
||||
.addv("rejectedCount", rejectedCount)
|
||||
.addv("doneCount", doneCount)
|
||||
.addv("satisfiedRate", percent(satisfiedCount, doneCount));
|
||||
}
|
||||
|
||||
private List<NutMap> caseResultData(String sessionId) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
d.code,
|
||||
d.name,
|
||||
COUNT(info.id) AS count
|
||||
FROM sys_dict parent
|
||||
INNER JOIN sys_dict d ON d.parentId = parent.id AND d.disabled = 0
|
||||
LEFT JOIN proposal_info info ON info.caseFilingResult = d.code AND info.sessionId = @sessionId
|
||||
WHERE parent.code = 'PROPOSAL_CASE_FILING_RESULT'
|
||||
GROUP BY d.id, d.code, d.name, d.location
|
||||
ORDER BY d.location
|
||||
""");
|
||||
sql.setParam("sessionId", sessionId);
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
private long satisfiedCount(String sessionId) {
|
||||
List<NutMap> dicts = feedbackDicts();
|
||||
List<NutMap> rows = feedbackRows(sessionId);
|
||||
return dicts.stream()
|
||||
.filter(dict -> {
|
||||
String name = dict.getString("name", "");
|
||||
return name.contains("满意") && !name.contains("不满意");
|
||||
})
|
||||
.mapToLong(dict -> rows.stream()
|
||||
.filter(row -> dict.getString("code", "").equals(row.getString("feedbackCode", "")))
|
||||
.count())
|
||||
.sum();
|
||||
}
|
||||
|
||||
private List<NutMap> feedbackDicts() {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT d.code, d.name
|
||||
FROM sys_dict parent
|
||||
INNER JOIN sys_dict d ON d.parentId = parent.id AND d.disabled = 0
|
||||
WHERE parent.code = 'PROPOSAL_FEEDBACK'
|
||||
ORDER BY d.location
|
||||
""");
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
private List<NutMap> feedbackRows(String sessionId) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
t.variable
|
||||
FROM proposal_info info
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
|
||||
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id
|
||||
AND t.taskState = 20
|
||||
AND t.taskName = 'feedback'
|
||||
WHERE ins.state = @finished
|
||||
AND info.sessionId = @sessionId
|
||||
GROUP BY info.id, t.variable
|
||||
""");
|
||||
sql.setParam("sessionId", sessionId);
|
||||
sql.setParam("finished", FINISHED_STATE);
|
||||
List<NutMap> rawRows = listMap(sql);
|
||||
List<NutMap> rows = new ArrayList<>();
|
||||
for (NutMap row : rawRows) {
|
||||
String variableStr = row.getString("variable", "");
|
||||
if (StrUtil.isBlank(variableStr)) {
|
||||
continue;
|
||||
}
|
||||
NutMap variable = Json.fromJson(NutMap.class, variableStr);
|
||||
rows.add(NutMap.NEW().addv("feedbackCode", variable.getString("tf_feedback", "")));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
private void setUnionFilter(Sql sql, String columnName, String varName) {
|
||||
String targetVarName = StrUtil.blankToDefault(varName, "unionFilter");
|
||||
if (canViewAllUnionData()) {
|
||||
sql.setVar(targetVarName, "");
|
||||
} else {
|
||||
sql.setVar(targetVarName, "AND " + columnName + " = @unionId");
|
||||
sql.setParam("unionId", SecurityUtil.getUnionId());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canViewAllUnionData() {
|
||||
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
|
||||
}
|
||||
|
||||
private BigDecimal decimalValue(NutMap map, String key) {
|
||||
BigDecimal value = map.getAs(key, BigDecimal.class);
|
||||
return value == null ? BigDecimal.ZERO : value;
|
||||
}
|
||||
|
||||
private List<NutMap> listMap(Sql sql) {
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(sql);
|
||||
return sql.getList(NutMap.class);
|
||||
}
|
||||
|
||||
private NutMap firstMap(Sql sql) {
|
||||
sql.setCallback(Sqls.callback.map());
|
||||
dao.execute(sql);
|
||||
NutMap map = sql.getObject(NutMap.class);
|
||||
return map == null ? NutMap.NEW() : map;
|
||||
}
|
||||
|
||||
private boolean containsAny(String text, String... keywords) {
|
||||
if (StrUtil.isBlank(text)) {
|
||||
return false;
|
||||
}
|
||||
for (String keyword : keywords) {
|
||||
if (text.contains(keyword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String percent(long count, long total) {
|
||||
if (total <= 0) {
|
||||
return "0%";
|
||||
}
|
||||
BigDecimal value = BigDecimal.valueOf(count)
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.divide(BigDecimal.valueOf(total), 1, RoundingMode.HALF_UP);
|
||||
return value.stripTrailingZeros().toPlainString() + "%";
|
||||
}
|
||||
|
||||
private BigDecimal percentValue(long count, long total) {
|
||||
if (total <= 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return BigDecimal.valueOf(count)
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.divide(BigDecimal.valueOf(total), 1, RoundingMode.HALF_UP)
|
||||
.stripTrailingZeros();
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 557 KiB |
|
After Width: | Height: | Size: 557 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 994 KiB |
|
After Width: | Height: | Size: 43 KiB |
@@ -0,0 +1,47 @@
|
||||
<!doctype html>
|
||||
<html lang="${lang,escape}">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>数智工会领导驾驶舱</title>
|
||||
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/css/root.css?v=20260524"/>
|
||||
<script src="${base!}/assets/platform/plugins/vue/vue.js?v=20260524"></script>
|
||||
<script src="${base!}/assets/platform/plugins/axios/axios.js?v=20260524"></script>
|
||||
<script src="${base!}/assets/platform/plugins/jquery/jquery.js?v=20260524"></script>
|
||||
<script src="${base!}/assets/platform/js/util/commonUtil.js?v=20260524"></script>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
Vue.config.devtools = false
|
||||
|
||||
const store = {
|
||||
state: {
|
||||
user: JSON.parse(window.sessionStorage.getItem("user") || "null") || {permissions: [], roles: []}
|
||||
}
|
||||
}
|
||||
|
||||
window.ELEMENT = window.ELEMENT || {
|
||||
Message: {
|
||||
error(message) {
|
||||
console.error(message || "操作失败")
|
||||
}
|
||||
},
|
||||
Loading: {
|
||||
service() {
|
||||
return {
|
||||
close() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$commonUtil = commonUtil
|
||||
Vue.prototype.$auth = commonUtil.authService()
|
||||
Vue.prototype.$axios = commonUtil.axiosService()
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
${layoutContent!}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,39 +1,69 @@
|
||||
const form = {
|
||||
template: /*language=HTML*/ `
|
||||
<el-dialog :title="formData.id ? '编辑' : '新增'" :visible.sync="dialogVisible" :close-on-click-modal="false">
|
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="80px">
|
||||
<el-form-item label="单位" prop="unitName">
|
||||
<el-input v-model="formData.unitName" placeholder="请输入单位" maxlength="100"
|
||||
show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="地点" prop="address">
|
||||
<el-input v-model="formData.address" placeholder="请输入地点" maxlength="100"
|
||||
show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="面积" prop="area">
|
||||
<el-input v-model="formData.area" placeholder="请输入面积" maxlength="10"
|
||||
show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="设施" prop="facility">
|
||||
<el-input v-model="formData.facility" type="textarea" :rows="5" placeholder="请输入设施"
|
||||
maxlength="1000" show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="开放时间" prop="openTime">
|
||||
<el-input v-model="formData.openTime" placeholder="请输入开放时间"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
`,
|
||||
template: [
|
||||
"<el-dialog :title=\"formData.id ? '编辑' : '新增'\" :visible.sync=\"dialogVisible\" :close-on-click-modal=\"false\" width=\"60%\">",
|
||||
" <el-form ref=\"formRef\" :model=\"formData\" :rules=\"rules\" label-width=\"110px\">",
|
||||
" <el-row :gutter=\"20\">",
|
||||
" <el-col :span=\"12\">",
|
||||
" <el-form-item label=\"当前工会\" prop=\"unionName\">",
|
||||
" <el-input v-model=\"formData.unionName\" disabled placeholder=\"当前登录人所在工会\"></el-input>",
|
||||
" </el-form-item>",
|
||||
" </el-col>",
|
||||
" <el-col :span=\"12\">",
|
||||
" <el-form-item label=\"当前单位\" prop=\"unitName\">",
|
||||
" <el-input v-model=\"formData.unitName\" disabled placeholder=\"当前登录人所在单位\"></el-input>",
|
||||
" </el-form-item>",
|
||||
" </el-col>",
|
||||
" </el-row>",
|
||||
" <el-form-item label=\"地点\" prop=\"address\">",
|
||||
" <el-input v-model=\"formData.address\" placeholder=\"请输入地点\" maxlength=\"100\" show-word-limit></el-input>",
|
||||
" </el-form-item>",
|
||||
" <el-form-item label=\"面积\" prop=\"area\">",
|
||||
" <el-input v-model=\"formData.area\" placeholder=\"请输入面积\" maxlength=\"10\" show-word-limit></el-input>",
|
||||
" </el-form-item>",
|
||||
" <el-form-item label=\"设施\" prop=\"facility\">",
|
||||
" <el-input v-model=\"formData.facility\" type=\"textarea\" :rows=\"5\" placeholder=\"请输入设施\" maxlength=\"1000\" show-word-limit></el-input>",
|
||||
" </el-form-item>",
|
||||
" <el-form-item label=\"开放时间\" prop=\"openTime\">",
|
||||
" <el-input v-model=\"formData.openTime\" placeholder=\"请输入开放时间\"></el-input>",
|
||||
" </el-form-item>",
|
||||
" <el-form-item label=\"图片/视频资料\" prop=\"mediaFiles\">",
|
||||
" <file-upload",
|
||||
" :value.sync=\"formData.mediaFiles\"",
|
||||
" :upload_number=\"10\"",
|
||||
" upload_mode=\"drag\"",
|
||||
" upload_result_category=\"array\"",
|
||||
" complete_result",
|
||||
" :accept=\"mediaAccept\">",
|
||||
" </file-upload>",
|
||||
" </el-form-item>",
|
||||
" <el-row :gutter=\"20\">",
|
||||
" <el-col :span=\"12\">",
|
||||
" <el-form-item label=\"地图X坐标\" prop=\"mapX\">",
|
||||
" <el-input v-model=\"formData.mapX\" disabled placeholder=\"请通过坐标按钮采集\"></el-input>",
|
||||
" </el-form-item>",
|
||||
" </el-col>",
|
||||
" <el-col :span=\"12\">",
|
||||
" <el-form-item label=\"地图Y坐标\" prop=\"mapY\">",
|
||||
" <el-input v-model=\"formData.mapY\" disabled placeholder=\"请通过坐标按钮采集\"></el-input>",
|
||||
" </el-form-item>",
|
||||
" </el-col>",
|
||||
" </el-row>",
|
||||
" </el-form>",
|
||||
" <div slot=\"footer\" class=\"dialog-footer\">",
|
||||
" <el-button @click=\"closeDialog\">取消</el-button>",
|
||||
" <el-button type=\"primary\" @click=\"onSubmit\">确定</el-button>",
|
||||
" </div>",
|
||||
"</el-dialog>"
|
||||
].join(""),
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
formData: {},
|
||||
currentUserOrg: {},
|
||||
mediaAccept: ".jpg,.jpeg,.png,.mp4,.mov",
|
||||
rules: {
|
||||
unitName: [{ required: true, message: "请输入单位", trigger: "blur" }],
|
||||
unitName: [{ required: true, message: "未获取到当前单位", trigger: "blur" }],
|
||||
unionName: [{ required: true, message: "未获取到当前工会", trigger: "blur" }],
|
||||
address: [{ required: true, message: "请输入地点", trigger: "blur" }],
|
||||
area: [{ required: true, message: "请输入面积", trigger: "blur" }],
|
||||
facility: [{ required: true, message: "请输入设施", trigger: "blur" }],
|
||||
@@ -42,23 +72,30 @@ const form = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(id) {
|
||||
async onOpen(id) {
|
||||
this.$set(this, "dialogVisible", true)
|
||||
this.resetForm()
|
||||
await this.loadCurrentUserOrg()
|
||||
if (id) {
|
||||
this.dialogVisible = true
|
||||
this.resetForm()
|
||||
$.post("/platform/buildHome/littleHouse/detail/" + id).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.formData = resp.data
|
||||
this.$set(this, "formData", Object.assign({}, this.currentUserOrg, resp.data, {
|
||||
mediaFiles: this.parseFiles(resp.data.mediaFiles)
|
||||
}))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.dialogVisible = true
|
||||
this.formData = {}
|
||||
this.$set(this, "formData", Object.assign({}, this.currentUserOrg, {
|
||||
mediaFiles: []
|
||||
}))
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef.clearValidate()
|
||||
})
|
||||
}
|
||||
},
|
||||
closeDialog() {
|
||||
this.$set(this, "dialogVisible", false)
|
||||
},
|
||||
resetForm() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.formRef) {
|
||||
@@ -67,14 +104,49 @@ const form = {
|
||||
}
|
||||
})
|
||||
},
|
||||
async loadCurrentUserOrg() {
|
||||
const resp = await $.post("/platform/buildHome/littleHouse/currentUserOrg")
|
||||
if (resp.code === 0) {
|
||||
this.$set(this, "currentUserOrg", resp.data || {})
|
||||
}
|
||||
},
|
||||
parseFiles(value) {
|
||||
if (!value) {
|
||||
return []
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
getFileExt(file) {
|
||||
const name = file.name || file.url || (file.response && file.response.data) || file.data || ""
|
||||
const index = name.lastIndexOf(".")
|
||||
return index > -1 ? name.substring(index + 1).toLowerCase() : ""
|
||||
},
|
||||
validateMediaFiles(files) {
|
||||
const acceptExts = this.mediaAccept.split(",").map(v => v.replace(".", "").toLowerCase())
|
||||
return (files || []).every(file => acceptExts.includes(this.getFileExt(file)))
|
||||
},
|
||||
onSubmit() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
$.post("/platform/buildHome/littleHouse/save", this.formData).then((resp) => {
|
||||
if (!this.validateMediaFiles(this.formData.mediaFiles)) {
|
||||
this.$message.warning("仅支持上传 jpg、jpeg、png、mp4、mov 格式文件")
|
||||
return
|
||||
}
|
||||
const data = Object.assign({}, this.formData, {
|
||||
mediaFiles: JSON.stringify(this.formData.mediaFiles || [])
|
||||
})
|
||||
$.post("/platform/buildHome/littleHouse/save", data).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.$emit("refresh")
|
||||
this.dialogVisible = false
|
||||
this.closeDialog()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ layout("/layouts/platform.html"){
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="单位名称">
|
||||
<el-input placeholder="单位名称" clearable v-model="pageForm.searchKeyword"></el-input>
|
||||
<search-item label="单位/工会名称">
|
||||
<el-input placeholder="单位/工会名称" clearable v-model="pageForm.searchKeyword"></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
@@ -18,6 +18,7 @@ layout("/layouts/platform.html"){
|
||||
</table-tool>
|
||||
<el-table :data="tableData" :size="tableSize" @sort-change="pageOrder" ref="table" row-key="id" style="width: 100%">
|
||||
<el-table-column :index="indexMethod" align="center" header-align="center" label="序号" type="index" width="80px"></el-table-column>
|
||||
<el-table-column label="工会" prop="unionName" sortable width="220px" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="单位" prop="unitName" sortable width="200px"></el-table-column>
|
||||
<el-table-column label="活动场所基本情况">
|
||||
<el-table-column label="地点" prop="address" sortable width="200px"></el-table-column>
|
||||
@@ -25,8 +26,9 @@ layout("/layouts/platform.html"){
|
||||
<el-table-column label="设施" prop="facility" sortable></el-table-column>
|
||||
<el-table-column label="开放时间" prop="openTime" sortable width="200px"></el-table-column>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="200px">
|
||||
<el-table-column label="操作" fixed="right" width="280px">
|
||||
<template scope="{row}">
|
||||
<el-button v-if="$auth.hasRole('SYSADMIN')" size="mini" type="warning" @click="openCoordinate(row)">坐标</el-button>
|
||||
<el-button size="mini" type="primary" @click="$refs.littleHouseFormRef.onOpen(row.id)">修改</el-button>
|
||||
<el-button size="mini" type="danger" @click="onDelete(row.id)">删除</el-button>
|
||||
</template>
|
||||
@@ -63,12 +65,78 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
})
|
||||
},
|
||||
openCoordinate(row) {
|
||||
if (!row || !row.id) {
|
||||
this.$message.warning("未获取到小家记录")
|
||||
return
|
||||
}
|
||||
if (this.hasCoordinate(row)) {
|
||||
this.$confirm(
|
||||
"当前记录已有坐标,是修改还是清空?",
|
||||
"提示",
|
||||
{
|
||||
type: "warning",
|
||||
distinguishCancelAndClose: true,
|
||||
confirmButtonText: "修改坐标",
|
||||
cancelButtonText: "清空坐标"
|
||||
}
|
||||
).then(() => {
|
||||
this.doOpenCoordinate(row)
|
||||
}).catch((action) => {
|
||||
if (action === "cancel") {
|
||||
this.clearCoordinate(row)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
this.doOpenCoordinate(row)
|
||||
},
|
||||
hasCoordinate(row) {
|
||||
return row
|
||||
&& row.mapX !== null
|
||||
&& row.mapX !== undefined
|
||||
&& row.mapX !== ""
|
||||
&& row.mapY !== null
|
||||
&& row.mapY !== undefined
|
||||
&& row.mapY !== ""
|
||||
},
|
||||
doOpenCoordinate(row) {
|
||||
const params = [
|
||||
"coordinateMode=1",
|
||||
"houseId=" + encodeURIComponent(row.id),
|
||||
"t=" + Date.now()
|
||||
]
|
||||
window.open("/platform/careData/leader?" + params.join("&"), "_blank")
|
||||
},
|
||||
clearCoordinate(row) {
|
||||
$.post("/platform/buildHome/littleHouse/clearCoordinate", { id: row.id }).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success("坐标已清空")
|
||||
this.doSearch()
|
||||
}
|
||||
})
|
||||
},
|
||||
handleCoordinateMessage(event) {
|
||||
if (event.origin !== window.location.origin) {
|
||||
return
|
||||
}
|
||||
const data = event.data || {}
|
||||
if (data.type !== "littleHouseCoordinateSaved") {
|
||||
return
|
||||
}
|
||||
this.$message.success("坐标采集成功")
|
||||
this.doSearch()
|
||||
},
|
||||
onExport() {
|
||||
this.$downLoad("/platform/buildHome/littleHouse/exportXlsx")
|
||||
}
|
||||
},
|
||||
created() {
|
||||
window.addEventListener("message", this.handleCoordinateMessage)
|
||||
this.pageData()
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("message", this.handleCoordinateMessage)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_leader_dashboard.html"){
|
||||
#-->
|
||||
<script src="${base!}/assets/platform/plugins/echarts/echarts.min.js?v=20260527"></script>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
font-family: "Microsoft YaHei", Arial, sans-serif;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dashboard-wrap {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
}
|
||||
|
||||
.dashboard-stage {
|
||||
position: relative;
|
||||
width: min(100vw, calc(100vh * 16 / 9));
|
||||
height: min(100vh, calc(100vw * 9 / 16));
|
||||
background: url("${base!}/assets/platform/images/careData/leader/leader-bg.jpg?v=20260524") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.asset-data-panel {
|
||||
position: absolute;
|
||||
left: 1.35%;
|
||||
top: 69.6%;
|
||||
width: 20.82%;
|
||||
height: 28.06%;
|
||||
background: url("${base!}/assets/platform/images/careData/leader/asset-data-panel.png?v=20260527") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.asset-data-title {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 1.3%;
|
||||
width: 100%;
|
||||
height: 10%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-family: "Microsoft YaHei", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.78);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.asset-data-chart {
|
||||
position: absolute;
|
||||
left: 3%;
|
||||
top: 14%;
|
||||
width: 94%;
|
||||
height: 74%;
|
||||
}
|
||||
|
||||
.asset-data-note {
|
||||
position: absolute;
|
||||
left: 8%;
|
||||
right: 8%;
|
||||
bottom: 4.5%;
|
||||
overflow: hidden;
|
||||
color: #315d86;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.asset-data-empty {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 48%;
|
||||
z-index: 2;
|
||||
color: rgba(36, 71, 105, 0.82);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fullscreen-btn {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
z-index: 20;
|
||||
height: 34px;
|
||||
min-width: 86px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid rgba(220, 246, 255, 0.72);
|
||||
border-radius: 4px;
|
||||
background: rgba(20, 101, 188, 0.58);
|
||||
color: #f4fbff;
|
||||
font-size: 13px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 14px rgba(64, 161, 255, 0.35);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.fullscreen-btn:hover {
|
||||
background: rgba(20, 126, 220, 0.72);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="dashboard-wrap" v-cloak>
|
||||
<main class="dashboard-stage">
|
||||
<section class="asset-data-panel">
|
||||
<div class="asset-data-title">资产数据</div>
|
||||
<div ref="usageChart" class="asset-data-chart"></div>
|
||||
<div class="asset-data-empty" v-if="!chartRows.length">暂无数据</div>
|
||||
<div class="asset-data-note">不同颜色代表不同使用状况</div>
|
||||
</section>
|
||||
</main>
|
||||
<button class="fullscreen-btn" type="button" @click="toggleFullscreen">{{ fullscreen ? "退出全屏" : "全屏显示" }}</button>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
chartRows: [],
|
||||
fullscreen: false,
|
||||
chartColors: ["#1c8df4", "#35c982", "#f3a64f", "#8a7cff", "#f05d80", "#2ec7c9", "#b6a2de"]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadAssetData()
|
||||
},
|
||||
mounted() {
|
||||
this.initChart()
|
||||
window.addEventListener("resize", this.resizeChart)
|
||||
document.addEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.addEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("resize", this.resizeChart)
|
||||
document.removeEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.removeEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
if (this.chart) {
|
||||
this.chart.dispose()
|
||||
this.$set(this, "chart", null)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
if (!window.echarts || !this.$refs.usageChart) {
|
||||
return
|
||||
}
|
||||
this.$set(this, "chart", echarts.init(this.$refs.usageChart))
|
||||
this.renderChart()
|
||||
},
|
||||
loadAssetData() {
|
||||
this.$axios.post("/platform/careData/leader/assetDataData").then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
this.$set(this, "chartRows", (data.states || [])
|
||||
.map(item => ({
|
||||
name: item.name || "未填写",
|
||||
value: Number(item.value || 0)
|
||||
}))
|
||||
.filter(item => item.value > 0))
|
||||
this.renderChart()
|
||||
})
|
||||
},
|
||||
renderChart() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
const total = this.chartRows.reduce((sum, item) => sum + item.value, 0)
|
||||
this.chart.setOption({
|
||||
color: this.chartColors,
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
formatter: "{b}<br/>{c} ({d}%)"
|
||||
},
|
||||
legend: {
|
||||
orient: "vertical",
|
||||
right: "3%",
|
||||
top: "24%",
|
||||
itemWidth: 8,
|
||||
itemHeight: 8,
|
||||
itemGap: 9,
|
||||
textStyle: {
|
||||
color: "#315d86",
|
||||
fontSize: 12,
|
||||
lineHeight: 14
|
||||
},
|
||||
formatter(name) {
|
||||
return name.length > 5 ? name.slice(0, 5) + "..." : name
|
||||
}
|
||||
},
|
||||
graphic: {
|
||||
type: "text",
|
||||
left: "28%",
|
||||
top: "47%",
|
||||
style: {
|
||||
text: total ? total + "\n件" : "",
|
||||
fill: "#1683f4",
|
||||
fontSize: 15,
|
||||
fontWeight: 700,
|
||||
textAlign: "center"
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: "使用状况",
|
||||
type: "pie",
|
||||
radius: ["32%", "56%"],
|
||||
center: ["32%", "52%"],
|
||||
avoidLabelOverlap: true,
|
||||
minAngle: 8,
|
||||
itemStyle: {
|
||||
borderColor: "rgba(255,255,255,0.9)",
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
color: "#244769",
|
||||
fontSize: 11,
|
||||
formatter: "{d}%"
|
||||
},
|
||||
labelLine: {
|
||||
length: 8,
|
||||
length2: 4,
|
||||
lineStyle: {
|
||||
color: "rgba(36, 71, 105, 0.55)"
|
||||
}
|
||||
},
|
||||
data: this.chartRows
|
||||
}]
|
||||
}, true)
|
||||
},
|
||||
resizeChart() {
|
||||
if (this.chart) {
|
||||
this.chart.resize()
|
||||
}
|
||||
},
|
||||
toggleFullscreen() {
|
||||
if (this.fullscreen) {
|
||||
const exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen
|
||||
exitFullscreen && exitFullscreen.call(document)
|
||||
return
|
||||
}
|
||||
const target = document.documentElement
|
||||
const requestFullscreen = target.requestFullscreen || target.webkitRequestFullscreen
|
||||
if (requestFullscreen) {
|
||||
const result = requestFullscreen.call(target)
|
||||
if (result && result.catch) {
|
||||
result.catch(() => {})
|
||||
}
|
||||
}
|
||||
},
|
||||
syncFullscreenState() {
|
||||
this.$set(this, "fullscreen", !!(document.fullscreenElement || document.webkitFullscreenElement))
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,257 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_leader_dashboard.html"){
|
||||
#-->
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
font-family: "Microsoft YaHei", "Microsoft JhengHei", Arial, sans-serif;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dashboard-wrap {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
}
|
||||
|
||||
.dashboard-stage {
|
||||
position: relative;
|
||||
width: min(100vw, calc(100vh * 16 / 9));
|
||||
height: min(100vh, calc(100vw * 9 / 16));
|
||||
background: url("${base!}/assets/platform/images/careData/leader/leader-bg.jpg?v=20260524") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.base-union-panel {
|
||||
position: absolute;
|
||||
right: calc(1.55% - 2px);
|
||||
top: calc(60.05% + 95px);
|
||||
width: calc(28.2% - 140px);
|
||||
height: 28.1%;
|
||||
background: url("${base!}/assets/platform/images/careData/leader/base-union-panel.png?v=20260526") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.base-union-title {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 1.3%;
|
||||
width: 100%;
|
||||
height: 10%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-family: "Microsoft YaHei", "Microsoft JhengHei", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.78);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.base-union-list {
|
||||
position: absolute;
|
||||
left: calc(8.5% - 15px);
|
||||
top: 20.2%;
|
||||
width: calc(83% + 35px);
|
||||
height: 68.5%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.base-union-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.base-union-list::-webkit-scrollbar-track {
|
||||
background: rgba(110, 178, 237, 0.22);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.base-union-list::-webkit-scrollbar-thumb {
|
||||
background: rgba(49, 142, 231, 0.76);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.base-union-row {
|
||||
position: relative;
|
||||
flex: 0 0 17.2%;
|
||||
height: 17.2%;
|
||||
min-height: 31px;
|
||||
background-position: center center;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
color: #244769;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.base-union-row:nth-child(2n + 1) {
|
||||
background-image: url("${base!}/assets/platform/images/careData/leader/base-union-row-a.png?v=20260526");
|
||||
}
|
||||
|
||||
.base-union-row:nth-child(2n) {
|
||||
background-image: url("${base!}/assets/platform/images/careData/leader/base-union-row-b.png?v=20260526");
|
||||
}
|
||||
|
||||
.base-union-name {
|
||||
position: absolute;
|
||||
left: 18%;
|
||||
top: 50%;
|
||||
width: 52%;
|
||||
overflow: hidden;
|
||||
transform: translateY(-50%);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.base-union-count {
|
||||
position: absolute;
|
||||
right: 9.5%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #1d82e8;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.base-union-count span {
|
||||
margin-left: 3px;
|
||||
color: #4e6d8d;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.base-union-empty {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 48%;
|
||||
color: rgba(36, 71, 105, 0.82);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fullscreen-btn {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
z-index: 20;
|
||||
height: 34px;
|
||||
min-width: 86px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid rgba(220, 246, 255, 0.72);
|
||||
border-radius: 4px;
|
||||
background: rgba(20, 101, 188, 0.58);
|
||||
color: #f4fbff;
|
||||
font-size: 13px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 14px rgba(64, 161, 255, 0.35);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.fullscreen-btn:hover {
|
||||
background: rgba(20, 126, 220, 0.72);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="dashboard-wrap" v-cloak>
|
||||
<main class="dashboard-stage">
|
||||
<section class="base-union-panel">
|
||||
<div class="base-union-title">基层工会</div>
|
||||
<div class="base-union-list" v-if="displayUnions.length">
|
||||
<div class="base-union-row" v-for="item in displayUnions" :key="item.unionCode || item.id">
|
||||
<div class="base-union-name" :title="item.unionname">{{ item.unionname || "--" }}</div>
|
||||
<div class="base-union-count">{{ item.value || 0 }}<span>人</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="base-union-empty" v-else>暂无数据</div>
|
||||
</section>
|
||||
</main>
|
||||
<button class="fullscreen-btn" type="button" @click="toggleFullscreen">{{ fullscreen ? "退出全屏" : "全屏显示" }}</button>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
unions: [],
|
||||
fullscreen: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
displayUnions() {
|
||||
return this.unions
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadBaseUnionData()
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.addEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.removeEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
methods: {
|
||||
loadBaseUnionData() {
|
||||
this.$axios.post("/platform/careData/leader/baseUnionData").then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
this.$set(this, "unions", data.unions || [])
|
||||
})
|
||||
},
|
||||
toggleFullscreen() {
|
||||
if (this.fullscreen) {
|
||||
const exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen
|
||||
exitFullscreen && exitFullscreen.call(document)
|
||||
return
|
||||
}
|
||||
const target = document.documentElement
|
||||
const requestFullscreen = target.requestFullscreen || target.webkitRequestFullscreen
|
||||
if (requestFullscreen) {
|
||||
const result = requestFullscreen.call(target)
|
||||
if (result && result.catch) {
|
||||
result.catch(() => {})
|
||||
}
|
||||
}
|
||||
},
|
||||
syncFullscreenState() {
|
||||
this.$set(this, "fullscreen", !!(document.fullscreenElement || document.webkitFullscreenElement))
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,252 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_leader_dashboard.html"){
|
||||
#-->
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
font-family: "Microsoft YaHei", "Microsoft JhengHei", Arial, sans-serif;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dashboard-wrap {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
}
|
||||
|
||||
.dashboard-stage {
|
||||
position: relative;
|
||||
width: min(100vw, calc(100vh * 16 / 9));
|
||||
height: min(100vh, calc(100vw * 9 / 16));
|
||||
background: url("${base!}/assets/platform/images/careData/leader/leader-bg.jpg?v=20260524") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.club-panel {
|
||||
position: absolute;
|
||||
right: calc(1.55% - 3px);
|
||||
top: calc(38.2% + 3px);
|
||||
width: calc(28.2% - 145px);
|
||||
height: 28.1%;
|
||||
background: url("${base!}/assets/platform/images/careData/leader/base-union-panel.png?v=20260526") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.club-title {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 1.3%;
|
||||
width: 100%;
|
||||
height: 10%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-family: "Microsoft YaHei", "Microsoft JhengHei", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.78);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.club-list {
|
||||
position: absolute;
|
||||
left: calc(8.5% - 14px);
|
||||
top: 20.2%;
|
||||
width: calc(83% + 29px);
|
||||
height: 68.5%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.club-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.club-list::-webkit-scrollbar-track {
|
||||
background: rgba(110, 178, 237, 0.22);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.club-list::-webkit-scrollbar-thumb {
|
||||
background: rgba(49, 142, 231, 0.76);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.club-row {
|
||||
position: relative;
|
||||
flex: 0 0 17.2%;
|
||||
height: 17.2%;
|
||||
min-height: 31px;
|
||||
background-position: center center;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
color: #244769;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.club-row:nth-child(2n + 1) {
|
||||
background-image: url("${base!}/assets/platform/images/careData/leader/base-union-row-a.png?v=20260526");
|
||||
}
|
||||
|
||||
.club-row:nth-child(2n) {
|
||||
background-image: url("${base!}/assets/platform/images/careData/leader/base-union-row-b.png?v=20260526");
|
||||
}
|
||||
|
||||
.club-name {
|
||||
position: absolute;
|
||||
left: 18%;
|
||||
top: 50%;
|
||||
width: 52%;
|
||||
overflow: hidden;
|
||||
transform: translateY(-50%);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.club-count {
|
||||
position: absolute;
|
||||
right: 9.5%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #1d82e8;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.club-count span {
|
||||
margin-left: 3px;
|
||||
color: #4e6d8d;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.club-empty {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 48%;
|
||||
color: rgba(36, 71, 105, 0.82);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fullscreen-btn {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
z-index: 20;
|
||||
height: 34px;
|
||||
min-width: 86px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid rgba(220, 246, 255, 0.72);
|
||||
border-radius: 4px;
|
||||
background: rgba(20, 101, 188, 0.58);
|
||||
color: #f4fbff;
|
||||
font-size: 13px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 14px rgba(64, 161, 255, 0.35);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.fullscreen-btn:hover {
|
||||
background: rgba(20, 126, 220, 0.72);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="dashboard-wrap" v-cloak>
|
||||
<main class="dashboard-stage">
|
||||
<section class="club-panel">
|
||||
<div class="club-title">协会社团</div>
|
||||
<div class="club-list" v-if="clubs.length">
|
||||
<div class="club-row" v-for="item in clubs" :key="item.clubCode || item.id">
|
||||
<div class="club-name" :title="item.clubName">{{ item.clubName || "--" }}</div>
|
||||
<div class="club-count">{{ item.value || 0 }}<span>人</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="club-empty" v-else>暂无数据</div>
|
||||
</section>
|
||||
</main>
|
||||
<button class="fullscreen-btn" type="button" @click="toggleFullscreen">{{ fullscreen ? "退出全屏" : "全屏显示" }}</button>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
clubs: [],
|
||||
fullscreen: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadClubData()
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.addEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.removeEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
methods: {
|
||||
loadClubData() {
|
||||
this.$axios.post("/platform/careData/leader/clubData").then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
this.$set(this, "clubs", data.clubs || [])
|
||||
})
|
||||
},
|
||||
toggleFullscreen() {
|
||||
if (this.fullscreen) {
|
||||
const exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen
|
||||
exitFullscreen && exitFullscreen.call(document)
|
||||
return
|
||||
}
|
||||
const target = document.documentElement
|
||||
const requestFullscreen = target.requestFullscreen || target.webkitRequestFullscreen
|
||||
if (requestFullscreen) {
|
||||
const result = requestFullscreen.call(target)
|
||||
if (result && result.catch) {
|
||||
result.catch(() => {})
|
||||
}
|
||||
}
|
||||
},
|
||||
syncFullscreenState() {
|
||||
this.$set(this, "fullscreen", !!(document.fullscreenElement || document.webkitFullscreenElement))
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,282 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_leader_dashboard.html"){
|
||||
#-->
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
font-family: "Microsoft YaHei", Arial, sans-serif;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dashboard-wrap {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
}
|
||||
|
||||
.dashboard-stage {
|
||||
position: relative;
|
||||
width: min(100vw, calc(100vh * 16 / 9));
|
||||
height: min(100vh, calc(100vw * 9 / 16));
|
||||
background: url("${base!}/assets/platform/images/careData/leader/leader-bg.jpg?v=20260524") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.condolence-panel {
|
||||
position: absolute;
|
||||
left: calc(1.35% - 5px);
|
||||
top: calc(38.9% - 28px);
|
||||
width: calc(20.82% - 6px);
|
||||
height: calc(27.65% + 70px);
|
||||
background: url("${base!}/assets/platform/images/careData/leader/condolence-panel.png?v=20260526") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.condolence-title {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 1.3%;
|
||||
width: 100%;
|
||||
height: 10%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-family: "Microsoft YaHei", "Microsoft JhengHei", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.78);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.condolence-chart {
|
||||
position: absolute;
|
||||
left: 4%;
|
||||
top: calc(18% + 10px);
|
||||
width: 92%;
|
||||
height: 72%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-around;
|
||||
padding: 2.5% 2.2% 7.5%;
|
||||
}
|
||||
|
||||
.condolence-bar-item {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 16%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.condolence-value {
|
||||
position: absolute;
|
||||
bottom: calc(17% + var(--bar-height) + 5px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: #243f5d;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 14px;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.condolence-bar {
|
||||
position: absolute;
|
||||
bottom: 17%;
|
||||
left: 50%;
|
||||
width: 42%;
|
||||
height: var(--bar-height);
|
||||
min-height: 0;
|
||||
transform: translateX(-50%);
|
||||
background: linear-gradient(180deg, rgba(95, 183, 255, 0.96), rgba(35, 126, 238, 0.88));
|
||||
border: 1px solid rgba(208, 241, 255, 0.66);
|
||||
box-shadow: inset 0 0 10px rgba(215, 244, 255, 0.4), 0 0 10px rgba(58, 152, 245, 0.46);
|
||||
}
|
||||
|
||||
.condolence-bar-item:nth-child(2n) .condolence-bar {
|
||||
background: linear-gradient(180deg, rgba(90, 219, 162, 0.94), rgba(40, 177, 103, 0.86));
|
||||
box-shadow: inset 0 0 10px rgba(224, 255, 239, 0.36), 0 0 10px rgba(54, 190, 118, 0.36);
|
||||
}
|
||||
|
||||
.condolence-bar-item:nth-child(3) .condolence-bar {
|
||||
background: linear-gradient(180deg, rgba(255, 183, 109, 0.94), rgba(229, 132, 66, 0.86));
|
||||
box-shadow: inset 0 0 10px rgba(255, 236, 211, 0.34), 0 0 10px rgba(227, 142, 76, 0.36);
|
||||
}
|
||||
|
||||
.condolence-label {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
width: 56px;
|
||||
overflow: hidden;
|
||||
transform: translateX(-50%);
|
||||
color: #244769;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.68);
|
||||
}
|
||||
|
||||
.condolence-empty {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 43%;
|
||||
color: rgba(36, 71, 105, 0.82);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fullscreen-btn {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
z-index: 20;
|
||||
height: 34px;
|
||||
min-width: 86px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid rgba(220, 246, 255, 0.72);
|
||||
border-radius: 4px;
|
||||
background: rgba(20, 101, 188, 0.58);
|
||||
color: #f4fbff;
|
||||
font-size: 13px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 14px rgba(64, 161, 255, 0.35);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.fullscreen-btn:hover {
|
||||
background: rgba(20, 126, 220, 0.72);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="dashboard-wrap" v-cloak>
|
||||
<main class="dashboard-stage">
|
||||
<section class="condolence-panel">
|
||||
<div class="condolence-title">慰问汇总</div>
|
||||
<div class="condolence-chart">
|
||||
<div class="condolence-empty" v-if="!hasData">暂无数据</div>
|
||||
<div class="condolence-bar-item" v-for="item in displayTypes" :key="item.index" :style="{ '--bar-height': barHeight(item) }">
|
||||
<div class="condolence-value">{{ item.rate || "0%" }}</div>
|
||||
<div class="condolence-bar"></div>
|
||||
<div class="condolence-label" :title="item.name">{{ shortName(item.name) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<button class="fullscreen-btn" type="button" @click="toggleFullscreen">{{ fullscreen ? "退出全屏" : "全屏显示" }}</button>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
types: [],
|
||||
fullscreen: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasData() {
|
||||
return this.types.some(item => Number(item.count || 0) > 0)
|
||||
},
|
||||
maxCount() {
|
||||
return Math.max(...this.types.map(item => Number(item.count || 0)), 0)
|
||||
},
|
||||
displayTypes() {
|
||||
const rows = this.types.slice(0, 5)
|
||||
while (rows.length < 5) {
|
||||
rows.push({
|
||||
index: rows.length + 1,
|
||||
name: "",
|
||||
rate: "0%",
|
||||
rateValue: 0
|
||||
})
|
||||
}
|
||||
return rows
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadCondolenceData()
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.addEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.removeEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
methods: {
|
||||
loadCondolenceData() {
|
||||
this.$axios.post("/platform/careData/leader/condolenceData").then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
this.$set(this, "types", data.types || [])
|
||||
})
|
||||
},
|
||||
shortName(name) {
|
||||
if (!name) {
|
||||
return "--"
|
||||
}
|
||||
return name.length > 4 ? name.slice(0, 4) : name
|
||||
},
|
||||
barHeight(item) {
|
||||
const count = Number(item.count || 0)
|
||||
if (count <= 0 || this.maxCount <= 0) {
|
||||
return "0%"
|
||||
}
|
||||
return Math.min(76, Math.max(8, count / this.maxCount * 76)) + "%"
|
||||
},
|
||||
toggleFullscreen() {
|
||||
if (this.fullscreen) {
|
||||
const exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen
|
||||
exitFullscreen && exitFullscreen.call(document)
|
||||
return
|
||||
}
|
||||
const target = document.documentElement
|
||||
const requestFullscreen = target.requestFullscreen || target.webkitRequestFullscreen
|
||||
if (requestFullscreen) {
|
||||
const result = requestFullscreen.call(target)
|
||||
if (result && result.catch) {
|
||||
result.catch(() => {})
|
||||
}
|
||||
}
|
||||
},
|
||||
syncFullscreenState() {
|
||||
this.$set(this, "fullscreen", !!(document.fullscreenElement || document.webkitFullscreenElement))
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,248 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_leader_dashboard.html"){
|
||||
#-->
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
font-family: "Microsoft YaHei", "Microsoft JhengHei", Arial, sans-serif;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dashboard-wrap {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
}
|
||||
|
||||
.dashboard-stage {
|
||||
position: relative;
|
||||
width: min(100vw, calc(100vh * 16 / 9));
|
||||
height: min(100vh, calc(100vw * 9 / 16));
|
||||
background: url("${base!}/assets/platform/images/careData/leader/leader-bg.jpg?v=20260524") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.data-metric-panel {
|
||||
position: absolute;
|
||||
right: calc(1.55% - 2px);
|
||||
top: 8.2%;
|
||||
width: calc(28.2% - 140px);
|
||||
height: 28.1%;
|
||||
background: url("${base!}/assets/platform/images/careData/leader/data-metric-panel.png?v=20260527") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.data-metric-title {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 1.3%;
|
||||
width: 100%;
|
||||
height: 10%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-family: "Microsoft YaHei", "Microsoft JhengHei", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.78);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.data-metric-graph {
|
||||
position: absolute;
|
||||
left: calc(8% + 5px);
|
||||
top: 21%;
|
||||
width: calc(84% + 5px);
|
||||
height: calc(66% + 20px);
|
||||
background: url("${base!}/assets/platform/images/careData/leader/data-metric-graph.png?v=20260527") center center / contain no-repeat;
|
||||
}
|
||||
|
||||
.data-metric-point {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 28%;
|
||||
min-width: 62px;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #1c79dc;
|
||||
font-family: "Microsoft YaHei", "Microsoft JhengHei", Arial, sans-serif;
|
||||
text-align: center;
|
||||
letter-spacing: 0;
|
||||
text-shadow: 0 0 8px rgba(255, 255, 255, 0.9), 0 0 10px rgba(72, 168, 255, 0.42);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.data-metric-point-center {
|
||||
width: 34%;
|
||||
}
|
||||
|
||||
.data-metric-label {
|
||||
margin-top: 2px;
|
||||
overflow: hidden;
|
||||
color: #315d86;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.data-metric-value {
|
||||
overflow: hidden;
|
||||
color: #1683f4;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 17px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.data-metric-point-center .data-metric-value {
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
.data-metric-unit {
|
||||
margin-left: 2px;
|
||||
color: #4e6d8d;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.fullscreen-btn {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
z-index: 20;
|
||||
height: 34px;
|
||||
min-width: 86px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid rgba(220, 246, 255, 0.72);
|
||||
border-radius: 4px;
|
||||
background: rgba(20, 101, 188, 0.58);
|
||||
color: #f4fbff;
|
||||
font-size: 13px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 14px rgba(64, 161, 255, 0.35);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.fullscreen-btn:hover {
|
||||
background: rgba(20, 126, 220, 0.72);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="dashboard-wrap" v-cloak>
|
||||
<main class="dashboard-stage">
|
||||
<section class="data-metric-panel">
|
||||
<div class="data-metric-title">数据指标</div>
|
||||
<div class="data-metric-graph">
|
||||
<div
|
||||
class="data-metric-point"
|
||||
v-for="item in dataMetricPoints"
|
||||
:key="item.key"
|
||||
:class="{ 'data-metric-point-center': item.center }"
|
||||
:style="{ left: item.left, top: item.top }">
|
||||
<div class="data-metric-value">{{ item.value }}<span class="data-metric-unit">{{ item.unit }}</span></div>
|
||||
<div class="data-metric-label">{{ item.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<button class="fullscreen-btn" type="button" @click="toggleFullscreen">{{ fullscreen ? "退出全屏" : "全屏显示" }}</button>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
dataMetric: {},
|
||||
fullscreen: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dataMetricPoints() {
|
||||
return [
|
||||
{key: "tourCount", label: "疗休养人数", value: this.displayNumber(this.dataMetric.tourCount), unit: "人", left: "12%", top: "19%"},
|
||||
{key: "honorCount", label: "劳模先进", value: this.displayNumber(this.dataMetric.honorCount), unit: "人", left: "88%", top: "19%"},
|
||||
{key: "difficultCount", label: "困难人数", value: this.displayNumber(this.dataMetric.difficultCount), unit: "人", left: "12%", top: "74%"},
|
||||
{key: "reimburseTotal", label: "报销总数", value: this.displayMoneyWan(this.dataMetric.reimburseTotal), unit: "万元", left: "88%", top: "74%"},
|
||||
{key: "budgetTotal", label: "预算总额", value: this.displayMoneyWan(this.dataMetric.budgetTotal), unit: "万元", left: "50%", top: "50%", center: true}
|
||||
]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadDataMetricData()
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.addEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.removeEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
methods: {
|
||||
loadDataMetricData() {
|
||||
this.$axios.post("/platform/careData/leader/dataMetricData").then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
this.$set(this, "dataMetric", res.data || {})
|
||||
})
|
||||
},
|
||||
displayNumber(value) {
|
||||
return Number(value || 0)
|
||||
},
|
||||
displayMoneyWan(value) {
|
||||
const amount = Number(value || 0) / 10000
|
||||
if (amount >= 100) {
|
||||
return Math.round(amount).toString()
|
||||
}
|
||||
if (amount >= 10) {
|
||||
return amount.toFixed(1).replace(/\.0$/, "")
|
||||
}
|
||||
return amount.toFixed(2).replace(/\.?0+$/, "")
|
||||
},
|
||||
toggleFullscreen() {
|
||||
if (this.fullscreen) {
|
||||
const exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen
|
||||
exitFullscreen && exitFullscreen.call(document)
|
||||
return
|
||||
}
|
||||
const target = document.documentElement
|
||||
const requestFullscreen = target.requestFullscreen || target.webkitRequestFullscreen
|
||||
if (requestFullscreen) {
|
||||
const result = requestFullscreen.call(target)
|
||||
if (result && result.catch) {
|
||||
result.catch(() => {})
|
||||
}
|
||||
}
|
||||
},
|
||||
syncFullscreenState() {
|
||||
this.$set(this, "fullscreen", !!(document.fullscreenElement || document.webkitFullscreenElement))
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,599 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_leader_dashboard.html"){
|
||||
#-->
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #071b36;
|
||||
font-family: "Microsoft YaHei", Arial, sans-serif;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.leader-screen {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
min-width: 1280px;
|
||||
min-height: 720px;
|
||||
overflow: hidden;
|
||||
color: #eaf7ff;
|
||||
background:
|
||||
radial-gradient(circle at 50% 45%, rgba(255, 255, 255, 0.62) 0, rgba(135, 202, 255, 0.4) 26%, transparent 58%),
|
||||
linear-gradient(135deg, #74b8f2 0%, #9acbfb 48%, #6aaee9 100%);
|
||||
}
|
||||
|
||||
.leader-screen::before,
|
||||
.leader-screen::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.leader-screen::before {
|
||||
border: 2px solid rgba(219, 245, 255, 0.58);
|
||||
box-shadow: inset 0 0 34px rgba(24, 126, 220, 0.34);
|
||||
}
|
||||
|
||||
.leader-screen::after {
|
||||
background:
|
||||
linear-gradient(rgba(255, 255, 255, 0.16) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.12) 1px, transparent 1px);
|
||||
background-size: 78px 78px;
|
||||
mask-image: radial-gradient(circle at 50% 50%, #000 0, transparent 76%);
|
||||
}
|
||||
|
||||
.screen-header {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: 82px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.screen-header::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 22%;
|
||||
right: 22%;
|
||||
height: 72px;
|
||||
border: 2px solid rgba(216, 246, 255, 0.58);
|
||||
border-top: none;
|
||||
border-radius: 0 0 88px 88px;
|
||||
background: linear-gradient(180deg, rgba(39, 144, 255, 0.42), rgba(27, 110, 205, 0.12));
|
||||
box-shadow: 0 12px 28px rgba(23, 94, 180, 0.28), inset 0 -12px 24px rgba(159, 230, 255, 0.36);
|
||||
}
|
||||
|
||||
.screen-title {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
color: #f8fdff;
|
||||
font-size: 36px;
|
||||
line-height: 58px;
|
||||
font-weight: 700;
|
||||
text-shadow: 0 0 10px rgba(40, 140, 232, 0.9);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.weather-box,
|
||||
.time-box {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
z-index: 3;
|
||||
color: rgba(255, 255, 255, 0.94);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.weather-box {
|
||||
left: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.weather-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle at 35% 35%, #ffe599, #ff9d32 58%, #74c8ff 60%, #d9f4ff 100%);
|
||||
box-shadow: 0 0 14px rgba(255, 207, 99, 0.45);
|
||||
}
|
||||
|
||||
.time-box {
|
||||
right: 28px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.screen-body {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: calc(100vh - 92px);
|
||||
padding: 6px 26px 22px;
|
||||
display: grid;
|
||||
grid-template-columns: 400px minmax(520px, 1fr) 400px;
|
||||
grid-template-rows: 300px 1fr 300px;
|
||||
gap: 18px 22px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
border: 1px solid rgba(232, 249, 255, 0.62);
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.18), rgba(77, 162, 229, 0.08));
|
||||
box-shadow: inset 0 0 24px rgba(255, 255, 255, 0.18), 0 10px 28px rgba(25, 103, 183, 0.16);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel::before,
|
||||
.panel::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
width: 42px;
|
||||
height: 2px;
|
||||
background: #ffe078;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.panel::before {
|
||||
left: 7px;
|
||||
}
|
||||
|
||||
.panel::after {
|
||||
right: 7px;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #f8fdff;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(90deg, rgba(31, 126, 238, 0.18), rgba(54, 159, 255, 0.62), rgba(31, 126, 238, 0.18));
|
||||
text-shadow: 0 0 8px rgba(60, 150, 240, 0.78);
|
||||
}
|
||||
|
||||
.proposal-panel {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px 20px;
|
||||
padding: 18px 16px 14px;
|
||||
}
|
||||
|
||||
.metric-item {
|
||||
position: relative;
|
||||
height: 62px;
|
||||
padding: 10px 12px 9px 70px;
|
||||
border: 1px solid rgba(227, 248, 255, 0.42);
|
||||
background: linear-gradient(90deg, rgba(231, 248, 255, 0.34), rgba(139, 205, 255, 0.12));
|
||||
box-shadow: inset 0 -8px 12px rgba(32, 124, 218, 0.16);
|
||||
}
|
||||
|
||||
.metric-item::after {
|
||||
content: "↑";
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
bottom: 8px;
|
||||
color: rgba(47, 142, 224, 0.72);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
top: 12px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, #f4fbff 0, #55c8ff 42%, rgba(34, 139, 227, 0.16) 68%);
|
||||
box-shadow: 0 0 14px rgba(77, 187, 255, 0.7);
|
||||
}
|
||||
|
||||
.metric-icon::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
right: 11px;
|
||||
top: 9px;
|
||||
height: 14px;
|
||||
border-radius: 2px;
|
||||
background: #1d97e6;
|
||||
box-shadow: -7px 9px 0 -2px #1d97e6, 7px 9px 0 -2px #1d97e6;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
color: rgba(38, 74, 112, 0.86);
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
margin-top: 3px;
|
||||
color: #1677d8;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.metric-value small {
|
||||
margin-left: 3px;
|
||||
color: rgba(49, 93, 132, 0.76);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.center-map {
|
||||
grid-column: 2;
|
||||
grid-row: 1 / 3;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.orbit {
|
||||
position: absolute;
|
||||
width: min(78%, 760px);
|
||||
aspect-ratio: 1;
|
||||
border: 1px dashed rgba(255, 255, 255, 0.6);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 60px rgba(255, 255, 255, 0.22);
|
||||
}
|
||||
|
||||
.map-shape {
|
||||
position: relative;
|
||||
width: min(76%, 760px);
|
||||
height: min(58%, 430px);
|
||||
border-radius: 46% 54% 47% 53% / 38% 45% 55% 62%;
|
||||
background:
|
||||
radial-gradient(circle at 74% 33%, rgba(243, 251, 255, 0.95) 0, rgba(68, 168, 255, 0.88) 22%, transparent 23%),
|
||||
radial-gradient(circle at 50% 52%, #5fb3ff 0, #2786f0 58%, #1b69cf 100%);
|
||||
box-shadow: 0 18px 38px rgba(35, 115, 210, 0.36), 0 0 38px rgba(255, 255, 255, 0.6);
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
.map-shape::before,
|
||||
.map-shape::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background: rgba(37, 129, 232, 0.9);
|
||||
box-shadow: 0 0 22px rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
|
||||
.map-shape::before {
|
||||
right: -62px;
|
||||
top: 92px;
|
||||
width: 120px;
|
||||
height: 132px;
|
||||
border-radius: 42% 58% 55% 45%;
|
||||
}
|
||||
|
||||
.map-shape::after {
|
||||
right: 42px;
|
||||
bottom: -42px;
|
||||
width: 55px;
|
||||
height: 82px;
|
||||
border-radius: 50%;
|
||||
transform: rotate(18deg);
|
||||
}
|
||||
|
||||
.center-session {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: rgba(20, 76, 134, 0.8);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.placeholder-list {
|
||||
padding: 18px 22px;
|
||||
}
|
||||
|
||||
.placeholder-row {
|
||||
display: grid;
|
||||
grid-template-columns: 34px 1fr 48px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: 40px;
|
||||
color: rgba(39, 73, 111, 0.82);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.placeholder-index {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
background: rgba(58, 151, 236, 0.72);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.placeholder-track {
|
||||
height: 5px;
|
||||
background: rgba(255, 255, 255, 0.38);
|
||||
}
|
||||
|
||||
.placeholder-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #41c79f, #3a96ec);
|
||||
}
|
||||
|
||||
.right-circle {
|
||||
height: calc(100% - 40px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.circle-core {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
background: radial-gradient(circle, #e8fbff 0, #42bbff 45%, #1f75da 100%);
|
||||
box-shadow: 0 0 30px rgba(38, 135, 230, 0.46);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.circle-core strong {
|
||||
font-size: 34px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 14px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
gap: 28px;
|
||||
}
|
||||
|
||||
.nav-dot {
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(238, 251, 255, 0.72);
|
||||
background: radial-gradient(circle, #ecfbff 0, #59bfff 52%, #2675d4 100%);
|
||||
box-shadow: 0 0 18px rgba(32, 126, 220, 0.42);
|
||||
}
|
||||
|
||||
@media (max-width: 1500px) {
|
||||
.screen-body {
|
||||
grid-template-columns: 360px minmax(480px, 1fr) 360px;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
gap: 12px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="leader-screen" v-cloak>
|
||||
<header class="screen-header">
|
||||
<div class="weather-box">
|
||||
<span class="weather-icon"></span>
|
||||
<span>智慧工会 · 领导驾驶舱</span>
|
||||
</div>
|
||||
<h1 class="screen-title">数智工会领导驾驶舱</h1>
|
||||
<div class="time-box">
|
||||
<div>{{ currentDate }}</div>
|
||||
<div>{{ currentTime }}</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="screen-body">
|
||||
<section class="panel proposal-panel">
|
||||
<div class="panel-title">提案征集与办理</div>
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-item">
|
||||
<span class="metric-icon"></span>
|
||||
<div class="metric-label">提案总数</div>
|
||||
<div class="metric-value">{{ overview.total || 0 }}<small>件</small></div>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-icon"></span>
|
||||
<div class="metric-label">立案数量</div>
|
||||
<div class="metric-value">{{ overview.filedCount || 0 }}<small>件</small></div>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-icon"></span>
|
||||
<div class="metric-label">意见建议梳理</div>
|
||||
<div class="metric-value">{{ overview.suggestionCount || 0 }}<small>件</small></div>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-icon"></span>
|
||||
<div class="metric-label">不予立案</div>
|
||||
<div class="metric-value">{{ overview.rejectedCount || 0 }}<small>件</small></div>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-icon"></span>
|
||||
<div class="metric-label">办结数量</div>
|
||||
<div class="metric-value">{{ overview.doneCount || 0 }}<small>件</small></div>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-icon"></span>
|
||||
<div class="metric-label">满意率</div>
|
||||
<div class="metric-value">{{ overview.satisfiedRate || '0%' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="center-map">
|
||||
<div class="center-session">{{ session.fullName || '当前届次' }}</div>
|
||||
<div class="orbit"></div>
|
||||
<div class="map-shape"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-title">专题指标总览</div>
|
||||
<div class="right-circle">
|
||||
<div class="circle-core">
|
||||
<strong>{{ overview.total || 0 }}</strong>
|
||||
<span>提案指标</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-title">办理进度排行</div>
|
||||
<div class="placeholder-list">
|
||||
<div class="placeholder-row" v-for="(item, index) in progressRows" :key="item.name">
|
||||
<span class="placeholder-index">{{ pad(index + 1) }}</span>
|
||||
<div>
|
||||
<div>{{ item.name }}</div>
|
||||
<div class="placeholder-track"><div class="placeholder-bar" :style="{width: item.rate}"></div></div>
|
||||
</div>
|
||||
<span>{{ item.rate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-title">专题模块预留</div>
|
||||
<div class="placeholder-list">
|
||||
<div class="placeholder-row" v-for="(item, index) in reserveRows" :key="item">
|
||||
<span class="placeholder-index">{{ pad(index + 1) }}</span>
|
||||
<div>{{ item }}</div>
|
||||
<span>待接入</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-title">数据趋势预留</div>
|
||||
<div class="placeholder-list">
|
||||
<div class="placeholder-row" v-for="item in trendRows" :key="item.name">
|
||||
<span class="placeholder-index">{{ item.index }}</span>
|
||||
<div>
|
||||
<div>{{ item.name }}</div>
|
||||
<div class="placeholder-track"><div class="placeholder-bar" :style="{width: item.rate}"></div></div>
|
||||
</div>
|
||||
<span>{{ item.rate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-title">重点数据预留</div>
|
||||
<div class="placeholder-list">
|
||||
<div class="placeholder-row" v-for="item in keyRows" :key="item.name">
|
||||
<span class="placeholder-index">{{ item.index }}</span>
|
||||
<div>{{ item.name }}</div>
|
||||
<span>{{ item.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<nav class="bottom-nav">
|
||||
<span class="nav-dot"></span>
|
||||
<span class="nav-dot"></span>
|
||||
<span class="nav-dot"></span>
|
||||
<span class="nav-dot"></span>
|
||||
<span class="nav-dot"></span>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
timer: null,
|
||||
currentDate: "",
|
||||
currentTime: "",
|
||||
session: {},
|
||||
overview: {},
|
||||
progressRows: [
|
||||
{name: "提案征集", rate: "87%"},
|
||||
{name: "立案办理", rate: "82%"},
|
||||
{name: "承办答复", rate: "68%"},
|
||||
{name: "结果反馈", rate: "43%"},
|
||||
{name: "满意评价", rate: "36%"}
|
||||
],
|
||||
reserveRows: ["职工关爱专题", "工会经费专题", "活动服务专题", "荣誉建设专题"],
|
||||
trendRows: [
|
||||
{index: "01", name: "年度提案趋势", rate: "78%"},
|
||||
{index: "02", name: "办理效率趋势", rate: "64%"},
|
||||
{index: "03", name: "满意度趋势", rate: "72%"}
|
||||
],
|
||||
keyRows: [
|
||||
{index: "01", name: "专题指标标题", value: "256"},
|
||||
{index: "02", name: "专题指标标题", value: "205"},
|
||||
{index: "03", name: "专题指标标题", value: "123"}
|
||||
]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.updateTime()
|
||||
this.$set(this, "timer", setInterval(this.updateTime, 1000))
|
||||
this.loadProposalData()
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer)
|
||||
},
|
||||
methods: {
|
||||
loadProposalData() {
|
||||
this.$axios.post("/platform/careData/leader/proposalData").then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
this.$set(this, "session", data.session || {})
|
||||
this.$set(this, "overview", data.overview || {})
|
||||
})
|
||||
},
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const week = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"][now.getDay()]
|
||||
this.$set(this, "currentDate", now.getFullYear() + "-" + this.pad(now.getMonth() + 1) + "-" + this.pad(now.getDate()) + " " + week)
|
||||
this.$set(this, "currentTime", this.pad(now.getHours()) + ":" + this.pad(now.getMinutes()) + ":" + this.pad(now.getSeconds()))
|
||||
},
|
||||
pad(value) {
|
||||
return String(value).padStart(2, "0")
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,142 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_leader_dashboard.html"){
|
||||
#-->
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
font-family: "Microsoft YaHei", Arial, sans-serif;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dashboard-wrap {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
}
|
||||
|
||||
.dashboard-stage {
|
||||
position: relative;
|
||||
width: min(100vw, calc(100vh * 16 / 9));
|
||||
height: min(100vh, calc(100vw * 9 / 16));
|
||||
background: url("${base!}/assets/platform/images/careData/leader/leader-bg.jpg?v=20260524") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
position: absolute;
|
||||
min-width: 86px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #1b83e9;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.75);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.metric-value::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -2px -8px;
|
||||
z-index: -1;
|
||||
background: rgba(189, 224, 252, 0.54);
|
||||
filter: blur(3px);
|
||||
}
|
||||
|
||||
.metric-value small {
|
||||
margin-left: 3px;
|
||||
color: rgba(43, 105, 159, 0.82);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.metric-total {
|
||||
left: 5.8%;
|
||||
top: 16.7%;
|
||||
}
|
||||
|
||||
.metric-filed {
|
||||
left: 15.9%;
|
||||
top: 16.7%;
|
||||
}
|
||||
|
||||
.metric-suggestion {
|
||||
left: 5.8%;
|
||||
top: 24.2%;
|
||||
}
|
||||
|
||||
.metric-rejected {
|
||||
left: 15.9%;
|
||||
top: 24.2%;
|
||||
}
|
||||
|
||||
.metric-done {
|
||||
left: 5.8%;
|
||||
top: 31.7%;
|
||||
}
|
||||
|
||||
.metric-satisfied {
|
||||
left: 15.9%;
|
||||
top: 31.7%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="dashboard-wrap" v-cloak>
|
||||
<main class="dashboard-stage">
|
||||
<div class="metric-value metric-total">{{ displayNumber(overview.total) }}<small>件</small></div>
|
||||
<div class="metric-value metric-filed">{{ displayNumber(overview.filedCount) }}<small>件</small></div>
|
||||
<div class="metric-value metric-suggestion">{{ displayNumber(overview.suggestionCount) }}<small>件</small></div>
|
||||
<div class="metric-value metric-rejected">{{ displayNumber(overview.rejectedCount) }}<small>件</small></div>
|
||||
<div class="metric-value metric-done">{{ displayNumber(overview.doneCount) }}<small>件</small></div>
|
||||
<div class="metric-value metric-satisfied">{{ overview.satisfiedRate || "0%" }}</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
overview: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadProposalData()
|
||||
},
|
||||
methods: {
|
||||
loadProposalData() {
|
||||
this.$axios.post("/platform/careData/leader/proposalData").then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
this.$set(this, "overview", data.overview || {})
|
||||
})
|
||||
},
|
||||
displayNumber(value) {
|
||||
return Number(value || 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,260 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_leader_dashboard.html"){
|
||||
#-->
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
font-family: "Microsoft YaHei", Arial, sans-serif;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dashboard-wrap {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
}
|
||||
|
||||
.dashboard-stage {
|
||||
position: relative;
|
||||
width: min(100vw, calc(100vh * 16 / 9));
|
||||
height: min(100vh, calc(100vw * 9 / 16));
|
||||
background: url("${base!}/assets/platform/images/careData/leader/leader-bg.jpg?v=20260524") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.proposal-panel {
|
||||
position: absolute;
|
||||
left: 1.35%;
|
||||
top: 8.2%;
|
||||
width: 20.82%;
|
||||
height: 27.65%;
|
||||
background: url("${base!}/assets/platform/images/careData/leader/proposal-panel.png?v=20260526") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.proposal-title {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 1.3%;
|
||||
width: 100%;
|
||||
height: 10%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-family: "Microsoft YaHei", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: 0;
|
||||
text-shadow: 0 0 6px rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
.proposal-card-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.proposal-card {
|
||||
position: absolute;
|
||||
width: 44%;
|
||||
height: 19.2%;
|
||||
background: var(--card-bg) center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.proposal-card-total {
|
||||
left: calc(4.3% - 2px);
|
||||
top: 18.5%;
|
||||
}
|
||||
|
||||
.proposal-card-filed {
|
||||
left: 51.7%;
|
||||
top: 18.5%;
|
||||
}
|
||||
|
||||
.proposal-card-total {
|
||||
height: calc(25.6% + 3px);
|
||||
}
|
||||
|
||||
.proposal-card-filed {
|
||||
height: calc(25.6% + 2px);
|
||||
}
|
||||
|
||||
.proposal-card-suggestion {
|
||||
left: calc(4.3% - 1px);
|
||||
top: calc(41.3% + 10px);
|
||||
height: calc(19.8% + 20px);
|
||||
}
|
||||
|
||||
.proposal-card-rejected {
|
||||
left: calc(51.7% + 2px);
|
||||
top: calc(41.3% + 10px);
|
||||
height: calc(19.8% + 20px);
|
||||
}
|
||||
|
||||
.proposal-card-done {
|
||||
left: calc(4.3% - 1px);
|
||||
top: calc(65.2% + 20px);
|
||||
height: calc(19.8% + 20px);
|
||||
}
|
||||
|
||||
.proposal-card-satisfied {
|
||||
left: 51.7%;
|
||||
top: calc(65.2% + 20px);
|
||||
height: calc(19.8% + 20px);
|
||||
}
|
||||
|
||||
.proposal-label {
|
||||
position: absolute;
|
||||
left: 38.5%;
|
||||
top: 20%;
|
||||
max-width: 50%;
|
||||
overflow: hidden;
|
||||
color: #304f70;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
letter-spacing: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.proposal-value {
|
||||
position: absolute;
|
||||
left: calc(38.5% + 23px);
|
||||
top: 48%;
|
||||
max-width: 50%;
|
||||
overflow: hidden;
|
||||
color: #1683f4;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 17px;
|
||||
letter-spacing: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 0 8px rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.fullscreen-btn {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
z-index: 20;
|
||||
height: 34px;
|
||||
min-width: 86px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid rgba(220, 246, 255, 0.72);
|
||||
border-radius: 4px;
|
||||
background: rgba(20, 101, 188, 0.58);
|
||||
color: #f4fbff;
|
||||
font-size: 13px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 14px rgba(64, 161, 255, 0.35);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.fullscreen-btn:hover {
|
||||
background: rgba(20, 126, 220, 0.72);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="dashboard-wrap" v-cloak>
|
||||
<main class="dashboard-stage">
|
||||
<section class="proposal-panel">
|
||||
<div class="proposal-title">提案征集和办理</div>
|
||||
<div class="proposal-card-grid">
|
||||
<article class="proposal-card" v-for="item in proposalMetrics" :key="item.key" :class="'proposal-card-' + item.key" :style="{ '--card-bg': 'url(' + item.bg + ')' }">
|
||||
<div class="proposal-label">{{ item.label }}</div>
|
||||
<div class="proposal-value">{{ item.value }}</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<button class="fullscreen-btn" type="button" @click="toggleFullscreen">{{ fullscreen ? "退出全屏" : "全屏显示" }}</button>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
overview: {},
|
||||
fullscreen: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
proposalMetrics() {
|
||||
const imagePath = "${base!}/assets/platform/images/careData/leader/"
|
||||
return [
|
||||
{key: "total", label: "提案总数", value: this.displayNumber(this.overview.total), bg: imagePath + "proposal-card-total.png?v=20260526"},
|
||||
{key: "filed", label: "立案数量", value: this.displayNumber(this.overview.filedCount), bg: imagePath + "proposal-card-filed.png?v=20260526"},
|
||||
{key: "suggestion", label: "意见建议", value: this.displayNumber(this.overview.suggestionCount), bg: imagePath + "proposal-card-suggestion.png?v=20260526"},
|
||||
{key: "rejected", label: "不予立案", value: this.displayNumber(this.overview.rejectedCount), bg: imagePath + "proposal-card-rejected.png?v=20260526"},
|
||||
{key: "done", label: "办结数量", value: this.displayNumber(this.overview.doneCount), bg: imagePath + "proposal-card-done.png?v=20260526"},
|
||||
{key: "satisfied", label: "满意率", value: this.overview.satisfiedRate || "0%", bg: imagePath + "proposal-card-satisfied.png?v=20260526"}
|
||||
]
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadProposalData()
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.addEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.removeEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
methods: {
|
||||
loadProposalData() {
|
||||
this.$axios.post("/platform/careData/leader/proposalData").then(res => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
this.$set(this, "overview", data.overview || {})
|
||||
})
|
||||
},
|
||||
displayNumber(value) {
|
||||
return Number(value || 0)
|
||||
},
|
||||
toggleFullscreen() {
|
||||
if (this.fullscreen) {
|
||||
const exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen
|
||||
exitFullscreen && exitFullscreen.call(document)
|
||||
return
|
||||
}
|
||||
const target = document.documentElement
|
||||
const requestFullscreen = target.requestFullscreen || target.webkitRequestFullscreen
|
||||
if (requestFullscreen) {
|
||||
const result = requestFullscreen.call(target)
|
||||
if (result && result.catch) {
|
||||
result.catch(() => {})
|
||||
}
|
||||
}
|
||||
},
|
||||
syncFullscreenState() {
|
||||
this.$set(this, "fullscreen", !!(document.fullscreenElement || document.webkitFullscreenElement))
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,173 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_leader_dashboard.html"){
|
||||
#-->
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
font-family: "Microsoft YaHei", "Microsoft JhengHei", Arial, sans-serif;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dashboard-wrap {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: #050f1f;
|
||||
}
|
||||
|
||||
.dashboard-stage {
|
||||
position: relative;
|
||||
width: min(100vw, calc(100vh * 16 / 9));
|
||||
height: min(100vh, calc(100vw * 9 / 16));
|
||||
background: url("${base!}/assets/platform/images/careData/leader/leader-bg.jpg?v=20260524") center center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.staff-home-panel {
|
||||
position: absolute;
|
||||
left: 24.1%;
|
||||
top: 22.5%;
|
||||
width: 53.1%;
|
||||
height: 63.5%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.staff-home-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
min-width: 168px;
|
||||
height: 30px;
|
||||
padding: 0 28px;
|
||||
transform: translateX(-50%);
|
||||
background: linear-gradient(90deg, rgba(78, 161, 246, 0.08), rgba(79, 169, 255, 0.88), rgba(78, 161, 246, 0.08));
|
||||
border-top: 1px solid rgba(214, 241, 255, 0.7);
|
||||
border-bottom: 1px solid rgba(83, 173, 255, 0.48);
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
letter-spacing: 0;
|
||||
text-shadow: 0 0 8px rgba(255, 255, 255, 0.85), 0 0 12px rgba(57, 151, 255, 0.72);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.staff-home-map-wrap {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 30px;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: radial-gradient(circle at center, rgba(181, 223, 255, 0.3), rgba(54, 145, 235, 0.08) 56%, rgba(54, 145, 235, 0));
|
||||
}
|
||||
|
||||
.staff-home-map {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -40px;
|
||||
top: -100px;
|
||||
width: calc(100% + 100px);
|
||||
height: calc(100% + 100px);
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
object-fit: fill;
|
||||
filter: drop-shadow(0 0 18px rgba(91, 177, 255, 0.52));
|
||||
}
|
||||
|
||||
.fullscreen-btn {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
z-index: 20;
|
||||
height: 34px;
|
||||
min-width: 86px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid rgba(220, 246, 255, 0.72);
|
||||
border-radius: 4px;
|
||||
background: rgba(20, 101, 188, 0.58);
|
||||
color: #f4fbff;
|
||||
font-size: 13px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 14px rgba(64, 161, 255, 0.35);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.fullscreen-btn:hover {
|
||||
background: rgba(20, 126, 220, 0.72);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="dashboard-wrap" v-cloak>
|
||||
<main class="dashboard-stage">
|
||||
<section class="staff-home-panel">
|
||||
<div class="staff-home-title">职工小家</div>
|
||||
<div class="staff-home-map-wrap">
|
||||
<img class="staff-home-map" src="${base!}/assets/platform/images/careData/leader/staff-home-campus-map.png?v=20260527" alt="">
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<button class="fullscreen-btn" type="button" @click="toggleFullscreen">{{ fullscreen ? "退出全屏" : "全屏显示" }}</button>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
fullscreen: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.addEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("fullscreenchange", this.syncFullscreenState)
|
||||
document.removeEventListener("webkitfullscreenchange", this.syncFullscreenState)
|
||||
},
|
||||
methods: {
|
||||
toggleFullscreen() {
|
||||
if (this.fullscreen) {
|
||||
const exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen
|
||||
exitFullscreen && exitFullscreen.call(document)
|
||||
return
|
||||
}
|
||||
const target = document.documentElement
|
||||
const requestFullscreen = target.requestFullscreen || target.webkitRequestFullscreen
|
||||
if (requestFullscreen) {
|
||||
const result = requestFullscreen.call(target)
|
||||
if (result && result.catch) {
|
||||
result.catch(() => {})
|
||||
}
|
||||
}
|
||||
},
|
||||
syncFullscreenState() {
|
||||
this.$set(this, "fullscreen", !!(document.fullscreenElement || document.webkitFullscreenElement))
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,850 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<van-nav-bar title="活动报销申请" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<van-form ref="formRef" class="form-container">
|
||||
<van-cell-group title="申请人信息" class="form-section">
|
||||
<van-field label="申请人姓名" v-model="formData.userName" readonly required :rules="[{ required: true }]"></van-field>
|
||||
<van-field label="申请人工号" v-model="formData.loginName" readonly required :rules="[{ required: true }]"></van-field>
|
||||
<van-field label="所属分工会" v-model="formData.unionName" readonly></van-field>
|
||||
</van-cell-group>
|
||||
|
||||
<van-cell-group title="报销信息" class="form-section">
|
||||
<van-field label="报销身份" v-model="formData.reimbursementIdentityName" placeholder="请选择报销身份"
|
||||
readonly is-link required :rules="[{ required: true }]" :disabled="!!taskId"
|
||||
@click="openIdentityPicker"></van-field>
|
||||
<van-popup v-model="showIdentityPicker" position="bottom" round>
|
||||
<van-picker show-toolbar :columns="reimbursementIdentityOption.map(function(i){return i.name})"
|
||||
@confirm="onIdentityConfirm" @cancel="showIdentityPicker = false"></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-field label="经费类型" v-model="formData.outlayManageSourceName" placeholder="请选择经费类型"
|
||||
readonly is-link required :rules="[{ required: true }]" @click="showBudgetTypePicker = true"></van-field>
|
||||
<van-popup v-model="showBudgetTypePicker" position="bottom" round>
|
||||
<van-picker show-toolbar :columns="budgetTypeOption.map(function(i){return i.name})"
|
||||
@confirm="onBudgetTypeConfirm" @cancel="showBudgetTypePicker = false"></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-field label="报销模式" v-model="formData.activityReimbursementModeName" placeholder="请选择报销模式"
|
||||
readonly is-link required :rules="[{ required: true }]" :disabled="isDailyReimbursementFixed"
|
||||
@click="openModePicker"></van-field>
|
||||
<van-popup v-model="showModePicker" position="bottom" round>
|
||||
<van-picker show-toolbar :columns="modeOption.map(function(i){return i.name})"
|
||||
@confirm="onModeConfirm" @cancel="showModePicker = false"></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-field label="所属协会" v-model="formData.clubName" placeholder="请选择协会"
|
||||
v-if="showClubField" readonly is-link required :rules="[{ required: true }]"
|
||||
@click="showClubPicker = true"></van-field>
|
||||
<van-popup v-model="showClubPicker" position="bottom" round>
|
||||
<van-picker show-toolbar :columns="clubOption.map(function(i){return i.clubName})"
|
||||
@confirm="onClubConfirm" @cancel="showClubPicker = false"></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-field label="项目名称" v-model="formData.activityName" placeholder="请选择活动"
|
||||
v-if="formData.activityReimbursementMode === 'activity'" readonly is-link required
|
||||
:rules="[{ required: true }]" @click="openActivityPicker"></van-field>
|
||||
<van-popup v-model="showActivityPicker" position="bottom" round>
|
||||
<van-picker show-toolbar :columns="activityOptions.map(function(i){return i.activityName + '(' + i.declareUnitName + ')'})"
|
||||
@confirm="onActivityConfirm" @cancel="showActivityPicker = false"></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<div class="inline-action" v-if="formData.activityReimbursementMode === 'activity' && formData.declareId">
|
||||
<van-button native-type="button" block plain type="info" @click="viewDeclare">查看申报信息</van-button>
|
||||
</div>
|
||||
|
||||
<van-field label="项目名称" v-model="formData.activityName" placeholder="请输入项目名称"
|
||||
v-if="formData.activityReimbursementMode !== 'activity'" required
|
||||
:rules="[{ required: true }]"></van-field>
|
||||
|
||||
<van-field label="扣款预算" v-model="formData.deductionBudgetName" placeholder="请选择扣款预算"
|
||||
readonly is-link required :rules="[{ required: true }]" @click="showBudgetPicker = true"></van-field>
|
||||
<van-popup v-model="showBudgetPicker" position="bottom" round>
|
||||
<van-picker show-toolbar :columns="budgetColumns"
|
||||
@confirm="onBudgetConfirm" @cancel="showBudgetPicker = false"></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<template v-if="formData.activityReimbursementMode === 'activity'">
|
||||
<van-field label="实际开始时间" v-model="formData.startTime" placeholder="请选择实际开始时间"
|
||||
readonly is-link required :rules="[{ required: true }]" @click="openDatePicker('startTime')"></van-field>
|
||||
<van-field label="实际结束时间" v-model="formData.endTime" placeholder="请选择实际结束时间"
|
||||
readonly is-link required :rules="[{ required: true }]" @click="openDatePicker('endTime')"></van-field>
|
||||
<van-field label="活动计划时间" name="planDate" :value="planDateText" placeholder="请选择带有活动计划时间的项目"
|
||||
readonly required :rules="[{ required: true, message: '请选择带有活动计划时间的项目' }]"></van-field>
|
||||
</template>
|
||||
</van-cell-group>
|
||||
|
||||
<van-popup v-model="showDatePicker" position="bottom" round>
|
||||
<van-datetime-picker v-model="datePickerValue" type="date" title="请选择日期"
|
||||
@confirm="onDateConfirm" @cancel="showDatePicker = false"></van-datetime-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-cell-group class="form-section">
|
||||
<template #title>
|
||||
<div class="section-title-row">
|
||||
<span>费用</span>
|
||||
<div>
|
||||
<van-tag size="large" type="primary" color="#ff3b30"
|
||||
v-if="formData.budgets && formData.budgets.length > 1"
|
||||
@click="removeActiveBudget">删除费用</van-tag>
|
||||
<van-tag size="large" type="primary" color="#1867b0" @click="addBudget">添加费用</van-tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<van-tabs v-model="activeBudgetIndex" type="card" color="#0e78c5" animated
|
||||
v-if="formData.budgets && formData.budgets.length > 0">
|
||||
<van-tab v-for="(item,index) in formData.budgets" :key="index" :name="index + ''" :title="'费用' + (index + 1)">
|
||||
<div class="fee-card">
|
||||
<van-field label="费用项目名称" v-model="item.name" placeholder="请输入费用项目名称"
|
||||
required :rules="[{ required: true }]"></van-field>
|
||||
<van-field label="预算费用" v-model="item.budgetPrice" placeholder="请输入预算费用"
|
||||
type="number" v-if="formData.activityReimbursementMode === 'activity'"
|
||||
required :rules="[{ required: true }]"></van-field>
|
||||
<van-field label="实际费用" v-model="item.actualPrice" placeholder="请输入实际费用"
|
||||
type="number" required :rules="[{ required: true }]"></van-field>
|
||||
<van-field label="收款人" v-model="item.username" placeholder="请输入收款人"
|
||||
required :rules="[{ required: true }]" @input="getCardNumberByPayeeId(item)"></van-field>
|
||||
<van-field label="支行名称" v-model="item.bankName" placeholder="请输入支行名称"
|
||||
required :rules="[{ required: true }]"></van-field>
|
||||
<van-field label="报销卡号" v-model="item.bankCardNum" placeholder="请输入报销卡号"
|
||||
type="number" required :rules="[{ required: true }]"></van-field>
|
||||
<van-field label="备注" v-model="item.remark" placeholder="请输入备注"></van-field>
|
||||
</div>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</van-cell-group>
|
||||
|
||||
<van-cell-group title="附件" class="form-section">
|
||||
<van-field class="direction-column-field" name="billFiles" label="发票等">
|
||||
<template #input>
|
||||
<h5-file-upload slot="input" :value.sync="formData.billFiles" :upload_number="20"
|
||||
upload_result_category="array" complete_result></h5-file-upload>
|
||||
</template>
|
||||
</van-field>
|
||||
<template v-if="formData.activityReimbursementMode === 'activity'">
|
||||
<van-field class="direction-column-field" name="photoFiles" label="活动报道照片/总结/链接">
|
||||
<template #input>
|
||||
<h5-file-upload slot="input" :value.sync="formData.photoFiles" :upload_number="20"
|
||||
upload_result_category="array" complete_result></h5-file-upload>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field class="direction-column-field" name="otherFiles" label="奖品/纪念品/服装/道具等活动人员名单">
|
||||
<template #input>
|
||||
<h5-file-upload slot="input" :value.sync="formData.otherFiles" :upload_number="20"
|
||||
upload_result_category="array" complete_result></h5-file-upload>
|
||||
</template>
|
||||
</van-field>
|
||||
</template>
|
||||
</van-cell-group>
|
||||
|
||||
<van-cell-group title="签字" class="form-section">
|
||||
<van-field class="direction-column-field" name="sign" label="" v-model="formData.sign" required
|
||||
:rules="[{ required: true, message: '请签字' }]">
|
||||
<template #input>
|
||||
<h5-signature v-model="formData.sign" slot="input"></h5-signature>
|
||||
</template>
|
||||
</van-field>
|
||||
</van-cell-group>
|
||||
|
||||
<div class="form-actions">
|
||||
<van-button native-type="button" round type="info" plain @click="onSave">保存</van-button>
|
||||
<van-button round type="info" @click="onSubmit" v-if="!taskId">提交</van-button>
|
||||
<van-button round type="info" @click="onFinishTask" v-else>提交</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
|
||||
<van-action-sheet v-model="declareDialogVisible" title="申报信息">
|
||||
<div class="detail-container">
|
||||
<van-cell-group title="基本信息">
|
||||
<van-cell title="活动名称">{{ declareViewData.activityName || '暂无' }}</van-cell>
|
||||
<van-cell title="活动类型">{{ getActivityTypeName(declareViewData.activityType) || '暂无' }}</van-cell>
|
||||
<van-cell title="申请人">{{ formatUser(declareViewData.userName, declareViewData.loginName) }}</van-cell>
|
||||
<van-cell :title="declareViewData.activityType === 'CLUB' ? '申请协会' : '申请单位'">{{ declareViewData.declareUnitName || '暂无' }}</van-cell>
|
||||
<van-cell title="联系方式">{{ declareViewData.mobile || '暂无' }}</van-cell>
|
||||
<van-cell title="申请时间">{{ formatDate(declareViewData.applyTime) }}</van-cell>
|
||||
<van-cell title="活动计划时间">{{ formatRange(declareViewData.planStartTime, declareViewData.planEndTime) }}</van-cell>
|
||||
<van-cell title="活动人数">{{ declareViewData.activityNumber || '暂无' }}</van-cell>
|
||||
<van-cell class="direction-column-cell" title="活动地址">{{ declareViewData.activityAddress || '暂无' }}</van-cell>
|
||||
</van-cell-group>
|
||||
<van-cell-group title="活动预算费用">
|
||||
<van-cell v-for="(item,index) in declareViewData.budgets" :key="index" class="direction-column-cell" :title="'费用' + (index + 1)">
|
||||
<div class="fee-row">费用项目名称:{{ item.name || '暂无' }}</div>
|
||||
<div class="fee-row">预算费用:{{ item.budgetPrice || 0 }}</div>
|
||||
</van-cell>
|
||||
<van-empty v-if="!declareViewData.budgets || declareViewData.budgets.length === 0" image="/assets/platform/plugins/vant-green/images/nodata/nodata.png" description="暂无数据"></van-empty>
|
||||
</van-cell-group>
|
||||
<van-cell-group title="活动方案及附件">
|
||||
<van-cell class="direction-column-cell" title="活动方案及简介">
|
||||
<div v-html="declareViewData.activityContent || '暂无'"></div>
|
||||
</van-cell>
|
||||
<van-cell class="direction-column-cell" title="附件">
|
||||
<file-preview v-if="declareViewData.files && declareViewData.files.length > 0" :files="declareViewData.files" complete_result></file-preview>
|
||||
<span v-else>暂无</span>
|
||||
</van-cell>
|
||||
<van-cell class="direction-column-cell" title="签字">
|
||||
<van-image v-if="declareViewData.sign" :src="declareViewData.sign" class="signature-image"></van-image>
|
||||
<span v-else>暂无</span>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
</van-action-sheet>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: '#app',
|
||||
store,
|
||||
dicts: ['ACTIVITY_BUDGET_TYPE'],
|
||||
data() {
|
||||
return {
|
||||
bizId: GetQueryString('bizId'),
|
||||
taskId: GetQueryString('taskId'),
|
||||
formData: {
|
||||
id: GetQueryString('businessId'),
|
||||
budgets: [],
|
||||
billFiles: [],
|
||||
photoFiles: [],
|
||||
otherFiles: []
|
||||
},
|
||||
activeBudgetIndex: '0',
|
||||
reimbursementIdentityOption: [],
|
||||
budgetTypeOption: [],
|
||||
modeOption: [
|
||||
{name: '活动报销', code: 'activity'},
|
||||
{name: '日常报销', code: 'daily'}
|
||||
],
|
||||
clubOption: [],
|
||||
activityOptions: [],
|
||||
budgetOption: [],
|
||||
showIdentityPicker: false,
|
||||
showBudgetTypePicker: false,
|
||||
showModePicker: false,
|
||||
showClubPicker: false,
|
||||
showActivityPicker: false,
|
||||
showBudgetPicker: false,
|
||||
showDatePicker: false,
|
||||
declareDialogVisible: false,
|
||||
declareViewData: {},
|
||||
activityDeclareReimbursementList: [],
|
||||
datePickerValue: new Date(),
|
||||
dateField: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showClubField() {
|
||||
return this.formData.outlayManageSource === 'ACTIVITY_BUDGET_TYPE_THREE' || this.formData.reimbursementIdentity === 'club'
|
||||
},
|
||||
isDailyReimbursementFixed() {
|
||||
return !this.hasSchoolLevelRole() && this.formData.outlayManageSource === 'ACTIVITY_BUDGET_TYPE_ONE'
|
||||
},
|
||||
filteredBudgetOption() {
|
||||
if (this.hasSchoolLevelRole()) {
|
||||
return this.budgetOption
|
||||
}
|
||||
if (this.formData.outlayManageSource === 'ACTIVITY_BUDGET_TYPE_ONE') {
|
||||
if (this.hasUnionRole() || this.hasClubRole()) {
|
||||
return this.budgetOption.filter((item) => {
|
||||
if (this.hasUnionRole() && this.hasClubRole()) {
|
||||
return item.isUnionUse === true || item.isClubUse === true
|
||||
}
|
||||
if (this.hasUnionRole()) {
|
||||
return item.isUnionUse === true
|
||||
}
|
||||
if (this.hasClubRole()) {
|
||||
return item.isClubUse === true
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return []
|
||||
}
|
||||
return this.budgetOption
|
||||
},
|
||||
budgetColumns() {
|
||||
return this.filteredBudgetOption.map((item) => {
|
||||
return this.getBudgetLabel(item)
|
||||
})
|
||||
},
|
||||
planDateText() {
|
||||
if (this.formData.planStartTime && this.formData.planEndTime) {
|
||||
return this.$moment(this.formData.planStartTime).format('YYYY-MM-DD') + ' ~ ' + this.$moment(this.formData.planEndTime).format('YYYY-MM-DD')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hasUnionRole() {
|
||||
return this.$auth.hasPermission('activityReimbursement.apply.unionHas')
|
||||
},
|
||||
hasClubRole() {
|
||||
return this.$auth.hasPermission('activityReimbursement.apply.clubHas')
|
||||
},
|
||||
hasSchoolLevelRole() {
|
||||
return this.$auth.hasPermission('activityReimbursement.apply.schoolHas')
|
||||
},
|
||||
openIdentityPicker() {
|
||||
if (!this.taskId) {
|
||||
this.showIdentityPicker = true
|
||||
}
|
||||
},
|
||||
openModePicker() {
|
||||
if (!this.isDailyReimbursementFixed) {
|
||||
this.showModePicker = true
|
||||
}
|
||||
},
|
||||
openActivityPicker() {
|
||||
if (!this.formData.outlayManageSource) {
|
||||
this.$toast.fail('请先选择经费类型')
|
||||
return
|
||||
}
|
||||
if (this.showClubField && !this.formData.clubId) {
|
||||
this.$toast.fail('请先选择所属协会')
|
||||
return
|
||||
}
|
||||
this.showActivityPicker = true
|
||||
},
|
||||
onIdentityConfirm(value, index) {
|
||||
const item = this.reimbursementIdentityOption[index]
|
||||
this.$set(this.formData, 'reimbursementIdentity', item.code)
|
||||
this.$set(this.formData, 'reimbursementIdentityName', item.name)
|
||||
this.reimbursementIdentityChange(item.code)
|
||||
this.showIdentityPicker = false
|
||||
},
|
||||
onBudgetTypeConfirm(value, index) {
|
||||
const item = this.budgetTypeOption[index]
|
||||
this.$set(this.formData, 'outlayManageSource', item.code)
|
||||
this.$set(this.formData, 'outlayManageSourceName', item.name)
|
||||
this.outlayManageSourceChange(item.code)
|
||||
this.showBudgetTypePicker = false
|
||||
},
|
||||
onModeConfirm(value, index) {
|
||||
const item = this.modeOption[index]
|
||||
this.$set(this.formData, 'activityReimbursementMode', item.code)
|
||||
this.$set(this.formData, 'activityReimbursementModeName', item.name)
|
||||
if (item.code === 'daily') {
|
||||
this.$set(this.formData, 'declareId', null)
|
||||
this.$set(this.formData, 'startTime', null)
|
||||
this.$set(this.formData, 'endTime', null)
|
||||
this.$set(this.formData, 'planStartTime', null)
|
||||
this.$set(this.formData, 'planEndTime', null)
|
||||
this.$set(this, 'declareViewData', {})
|
||||
}
|
||||
this.getActivityReimbursementByUser()
|
||||
this.showModePicker = false
|
||||
},
|
||||
onClubConfirm(value, index) {
|
||||
const item = this.clubOption[index]
|
||||
this.$set(this.formData, 'clubId', item.id)
|
||||
this.$set(this.formData, 'clubName', item.clubName)
|
||||
this.$set(this.formData, 'declareUnitName', item.clubName)
|
||||
this.$set(this.formData, 'deductionBudgetId', null)
|
||||
this.$set(this.formData, 'deductionBudgetName', null)
|
||||
this.getBudgetByYear()
|
||||
this.getActivityReimbursementByUser()
|
||||
this.showClubPicker = false
|
||||
},
|
||||
onActivityConfirm(value, index) {
|
||||
const item = this.activityOptions[index]
|
||||
this.activityChange(item.id)
|
||||
this.showActivityPicker = false
|
||||
},
|
||||
onBudgetConfirm(value, index) {
|
||||
const item = this.filteredBudgetOption[index]
|
||||
this.$set(this.formData, 'deductionBudgetId', item.id)
|
||||
this.$set(this.formData, 'deductionBudgetName', this.getBudgetLabel(item))
|
||||
this.showBudgetPicker = false
|
||||
},
|
||||
reimbursementIdentityChange(val) {
|
||||
this.$set(this.formData, 'deductionBudgetId', null)
|
||||
this.$set(this.formData, 'deductionBudgetName', null)
|
||||
this.$set(this.formData, 'unionId', this.$store.state.user.union.id)
|
||||
if (val === 'school') {
|
||||
this.$set(this.formData, 'declareUnitName', '校工会')
|
||||
} else if (val === 'union') {
|
||||
this.$set(this.formData, 'declareUnitName', this.$store.state.user.union.name)
|
||||
} else if (val === 'club') {
|
||||
this.$set(this.formData, 'declareUnitName', this.formData.clubName || '')
|
||||
}
|
||||
},
|
||||
outlayManageSourceChange(val) {
|
||||
this.$set(this.formData, 'clubId', null)
|
||||
this.$set(this.formData, 'clubName', null)
|
||||
this.$set(this.formData, 'deductionBudgetId', null)
|
||||
this.$set(this.formData, 'deductionBudgetName', null)
|
||||
this.$set(this.formData, 'declareId', null)
|
||||
this.$set(this.formData, 'activityName', null)
|
||||
this.$set(this.formData, 'budgets', [])
|
||||
this.$set(this, 'activeBudgetIndex', '0')
|
||||
this.$set(this.formData, 'startTime', null)
|
||||
this.$set(this.formData, 'endTime', null)
|
||||
this.$set(this.formData, 'planStartTime', null)
|
||||
this.$set(this.formData, 'planEndTime', null)
|
||||
this.$set(this, 'declareViewData', {})
|
||||
if (val === 'ACTIVITY_BUDGET_TYPE_ONE' && !this.hasSchoolLevelRole()) {
|
||||
this.$set(this.formData, 'activityReimbursementMode', 'daily')
|
||||
this.$set(this.formData, 'activityReimbursementModeName', '日常报销')
|
||||
}
|
||||
this.getBudgetByYear()
|
||||
this.getActivityReimbursementByUser()
|
||||
},
|
||||
getBudgetByYear() {
|
||||
this.budgetOption = []
|
||||
this.$axios.post('/platform/activityReimbursement/apply/getBudgetByYear', {
|
||||
outlayManageSource: this.formData.outlayManageSource,
|
||||
clubId: this.formData.clubId
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.budgetOption = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
getBudgetLabel(item) {
|
||||
if (!item) {
|
||||
return ''
|
||||
}
|
||||
const shortName = this.getShortBudgetName(item.activityMatter)
|
||||
if (this.hasSchoolLevelRole()) {
|
||||
return this.getBudgetMoneyText(item) + ',名称:' + shortName
|
||||
}
|
||||
if (this.formData.outlayManageSource === 'ACTIVITY_BUDGET_TYPE_ONE' && (this.hasUnionRole() || this.hasClubRole())) {
|
||||
return '名称:' + shortName
|
||||
}
|
||||
return this.getBudgetMoneyText(item) + ',名称:' + shortName
|
||||
},
|
||||
// 预算名称过长会挤掉金额,手机端选择器中统一截断名称并优先展示金额。
|
||||
getShortBudgetName(name) {
|
||||
const text = name || '暂无'
|
||||
if (text.length > 12) {
|
||||
return text.substring(0, 12) + '...'
|
||||
}
|
||||
return text
|
||||
},
|
||||
getBudgetMoneyText(item) {
|
||||
return '预算:' + this.formatBudgetMoney(item.totalBudgetMoney) + ',已用:' + this.formatBudgetMoney(item.usedBudgetMoney)
|
||||
},
|
||||
formatBudgetMoney(value) {
|
||||
if (this.isEmptyValue(value)) {
|
||||
return '0'
|
||||
}
|
||||
return value
|
||||
},
|
||||
getActivityReimbursementByUser() {
|
||||
if (this.formData.activityReimbursementMode === 'daily') {
|
||||
this.activityOptions = []
|
||||
return
|
||||
}
|
||||
this.$axios.post('/platform/activityReimbursement/apply/getActivityReimbursementByUser', {
|
||||
id: this.bizId,
|
||||
outlayManageSource: this.formData.outlayManageSource,
|
||||
clubId: this.formData.clubId
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.activityOptions = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
activityChange(id) {
|
||||
this.$axios.post('/platform/activityDeclare/mine/findOne', {id: id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = this.normalizeDeclareData(res.data || {})
|
||||
this.$set(this, 'declareViewData', data)
|
||||
this.mergeFormData(data)
|
||||
this.$set(this.formData, 'declareId', id)
|
||||
this.$set(this.formData, 'id', this.bizId ? this.bizId : null)
|
||||
this.fillCurrentUser()
|
||||
}
|
||||
})
|
||||
},
|
||||
mergeFormData(data) {
|
||||
Object.keys(data).forEach((key) => {
|
||||
this.$set(this.formData, key, data[key])
|
||||
})
|
||||
},
|
||||
normalizeDeclareData(data) {
|
||||
data.budgets = this.parseJsonArray(data.budgets)
|
||||
data.files = this.parseJsonArray(data.files)
|
||||
return data
|
||||
},
|
||||
viewDeclare() {
|
||||
if (!this.formData.declareId) {
|
||||
this.$toast.fail('请先选择项目')
|
||||
return
|
||||
}
|
||||
if (this.declareViewData && this.declareViewData.id === this.formData.declareId) {
|
||||
this.declareDialogVisible = true
|
||||
return
|
||||
}
|
||||
this.$axios.post('/platform/activityDeclare/mine/findOne', {id: this.formData.declareId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$set(this, 'declareViewData', this.normalizeDeclareData(res.data || {}))
|
||||
this.declareDialogVisible = true
|
||||
}
|
||||
})
|
||||
},
|
||||
addBudget() {
|
||||
const budgets = this.formData.budgets || []
|
||||
budgets.push({})
|
||||
this.$set(this.formData, 'budgets', budgets)
|
||||
this.$set(this, 'activeBudgetIndex', String(budgets.length - 1))
|
||||
},
|
||||
removeActiveBudget() {
|
||||
this.removeBudget(Number(this.activeBudgetIndex || 0))
|
||||
},
|
||||
removeBudget(index) {
|
||||
const budgets = this.formData.budgets || []
|
||||
if (budgets.length <= 1) {
|
||||
this.$toast.fail('至少保留一条费用')
|
||||
return
|
||||
}
|
||||
budgets.splice(index, 1)
|
||||
this.$set(this.formData, 'budgets', budgets)
|
||||
const active = index - 1 >= 0 ? index - 1 : 0
|
||||
this.$set(this, 'activeBudgetIndex', String(active))
|
||||
},
|
||||
getCardNumberByPayeeId(row) {
|
||||
if (!row.username) {
|
||||
return
|
||||
}
|
||||
this.$axios.post('/platform/activityReimbursement/apply/getCardNumberByPayeeId', {username: row.username}).then((res) => {
|
||||
if (res.code === 0 && res.data) {
|
||||
this.$set(row, 'bankCardNum', res.data.bankCardNum)
|
||||
this.$set(row, 'bankName', res.data.bankName)
|
||||
}
|
||||
})
|
||||
},
|
||||
openDatePicker(field) {
|
||||
this.dateField = field
|
||||
this.datePickerValue = this.formData[field] ? new Date(this.formData[field]) : new Date()
|
||||
this.showDatePicker = true
|
||||
},
|
||||
onDateConfirm(value) {
|
||||
this.$set(this.formData, this.dateField, this.$moment(value).format('YYYY-MM-DD'))
|
||||
this.showDatePicker = false
|
||||
},
|
||||
isEmptyValue(value) {
|
||||
return value === null || value === undefined || value === ''
|
||||
},
|
||||
isInvalidNumber(value, min) {
|
||||
if (this.isEmptyValue(value)) {
|
||||
return true
|
||||
}
|
||||
const numberValue = Number(value)
|
||||
return isNaN(numberValue) || numberValue < min
|
||||
},
|
||||
validateReimbursementRelation() {
|
||||
if (this.formData.reimbursementIdentity === 'union' && this.formData.outlayManageSource === 'ACTIVITY_BUDGET_TYPE_THREE') {
|
||||
this.$toast.fail('报销身份如果是分工会,不能报销协会经费')
|
||||
return false
|
||||
}
|
||||
if (this.formData.reimbursementIdentity === 'club' && this.formData.outlayManageSource === 'ACTIVITY_BUDGET_TYPE_TWO') {
|
||||
this.$toast.fail('报销身份如果是协会,不能报销分工会经费')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
// 按 PC 端必填规则逐项校验,避免自定义组件和条件字段漏校验。
|
||||
validateForm() {
|
||||
if (this.isEmptyValue(this.formData.reimbursementIdentity)) {
|
||||
this.$toast.fail('请选择报销身份')
|
||||
return false
|
||||
}
|
||||
if (this.isEmptyValue(this.formData.outlayManageSource)) {
|
||||
this.$toast.fail('请选择经费类型')
|
||||
return false
|
||||
}
|
||||
if (this.isEmptyValue(this.formData.activityReimbursementMode)) {
|
||||
this.$toast.fail('请选择报销模式')
|
||||
return false
|
||||
}
|
||||
if (this.showClubField && this.isEmptyValue(this.formData.clubId)) {
|
||||
this.$toast.fail('请选择所属协会')
|
||||
return false
|
||||
}
|
||||
if (this.formData.activityReimbursementMode === 'activity') {
|
||||
if (this.isEmptyValue(this.formData.declareId)) {
|
||||
this.$toast.fail('请选择活动')
|
||||
return false
|
||||
}
|
||||
} else if (this.isEmptyValue(this.formData.activityName)) {
|
||||
this.$toast.fail('请输入项目名称')
|
||||
return false
|
||||
}
|
||||
if (this.isEmptyValue(this.formData.deductionBudgetId)) {
|
||||
this.$toast.fail('请选择扣款预算')
|
||||
return false
|
||||
}
|
||||
if (this.formData.activityReimbursementMode === 'activity') {
|
||||
if (this.isEmptyValue(this.formData.startTime)) {
|
||||
this.$toast.fail('请选择实际开始时间')
|
||||
return false
|
||||
}
|
||||
if (this.isEmptyValue(this.formData.endTime)) {
|
||||
this.$toast.fail('请选择实际结束时间')
|
||||
return false
|
||||
}
|
||||
if (this.isEmptyValue(this.formData.planStartTime) || this.isEmptyValue(this.formData.planEndTime)) {
|
||||
this.$toast.fail('请选择带有活动计划时间的项目')
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (!this.formData.budgets || this.formData.budgets.length === 0) {
|
||||
this.$toast.fail('请填写费用')
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < this.formData.budgets.length; i++) {
|
||||
const item = this.formData.budgets[i] || {}
|
||||
const label = '费用' + (i + 1)
|
||||
if (this.isEmptyValue(item.name)) {
|
||||
this.$toast.fail('请填写' + label + '费用项目名称')
|
||||
return false
|
||||
}
|
||||
if (this.formData.activityReimbursementMode === 'activity' && this.isInvalidNumber(item.budgetPrice, 1)) {
|
||||
this.$toast.fail('请填写' + label + '预算费用')
|
||||
return false
|
||||
}
|
||||
if (this.isInvalidNumber(item.actualPrice, 0)) {
|
||||
this.$toast.fail('请填写' + label + '实际费用')
|
||||
return false
|
||||
}
|
||||
if (this.isEmptyValue(item.username)) {
|
||||
this.$toast.fail('请填写' + label + '收款人')
|
||||
return false
|
||||
}
|
||||
if (this.isEmptyValue(item.bankName)) {
|
||||
this.$toast.fail('请填写' + label + '支行名称')
|
||||
return false
|
||||
}
|
||||
if (this.isEmptyValue(item.bankCardNum)) {
|
||||
this.$toast.fail('请填写' + label + '报销卡号')
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (this.isEmptyValue(this.formData.sign)) {
|
||||
this.$toast.fail('请签字')
|
||||
return false
|
||||
}
|
||||
return this.validateReimbursementRelation()
|
||||
},
|
||||
onSave() {
|
||||
if (!this.validateReimbursementRelation()) {
|
||||
return
|
||||
}
|
||||
this.$dialog.confirm({title: '提示', message: '您确定保存吗?'}).then(() => {
|
||||
this.$axios.post('/platform/activityReimbursement/apply/save', {data: JSON.stringify(this.formData)}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success(res.msg)
|
||||
this.$pjaxReplace('/platform/activityReimbursement/mine/h5')
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
onSubmit() {
|
||||
if (!this.validateForm()) {
|
||||
return
|
||||
}
|
||||
this.$refs.formRef.validate().then(() => {
|
||||
this.$dialog.confirm({title: '提示', message: '您确定要提交申请吗?'}).then(() => {
|
||||
this.$axios.post('/platform/activityReimbursement/apply/submit', {data: JSON.stringify(this.formData)}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success(res.msg)
|
||||
this.$pjaxReplace('/platform/activityReimbursement/mine/h5')
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
}).catch(() => {})
|
||||
},
|
||||
onFinishTask() {
|
||||
if (!this.validateForm()) {
|
||||
return
|
||||
}
|
||||
this.$refs.formRef.validate().then(() => {
|
||||
this.$dialog.confirm({title: '提示', message: '您确定要提交申请吗?'}).then(() => {
|
||||
this.$axios.post('/platform/activityReimbursement/apply/submitAgain', {
|
||||
data: JSON.stringify(this.formData),
|
||||
taskId: this.taskId
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success(res.msg)
|
||||
this.$pjaxReplace('/platform/activityReimbursement/mine/h5')
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
}).catch(() => {})
|
||||
},
|
||||
parseJsonArray(value) {
|
||||
if (!value) {
|
||||
return []
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
findOne() {
|
||||
this.$axios.post('/platform/activityReimbursement/mine/findOne', {id: this.bizId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
data.billFiles = this.parseJsonArray(data.billFiles)
|
||||
data.photoFiles = this.parseJsonArray(data.photoFiles)
|
||||
data.otherFiles = this.parseJsonArray(data.otherFiles)
|
||||
data.budgets = this.parseJsonArray(data.budgets)
|
||||
this.$set(this, 'activeBudgetIndex', '0')
|
||||
this.$set(this, 'formData', {})
|
||||
this.mergeFormData(data)
|
||||
if (data.declareId) {
|
||||
this.$set(this, 'declareViewData', this.normalizeDeclareData({
|
||||
id: data.declareId,
|
||||
activityName: data.activityName,
|
||||
activityType: data.activityType,
|
||||
userName: data.userName,
|
||||
loginName: data.loginName,
|
||||
declareUnitName: data.declareUnitName,
|
||||
mobile: data.mobile,
|
||||
applyTime: data.applyTime,
|
||||
planStartTime: data.planStartTime,
|
||||
planEndTime: data.planEndTime,
|
||||
activityNumber: data.activityNumber,
|
||||
activityAddress: data.activityAddress,
|
||||
budgets: data.budgets,
|
||||
activityContent: data.activityContent,
|
||||
files: data.files,
|
||||
sign: data.sign
|
||||
}))
|
||||
}
|
||||
this.patchDisplayNames()
|
||||
this.getBudgetByYear()
|
||||
this.getActivityReimbursementByUser()
|
||||
}
|
||||
})
|
||||
},
|
||||
patchDisplayNames() {
|
||||
const mode = this.modeOption.find((item) => item.code === this.formData.activityReimbursementMode)
|
||||
if (mode) {
|
||||
this.$set(this.formData, 'activityReimbursementModeName', mode.name)
|
||||
}
|
||||
const type = this.budgetTypeOption.find((item) => item.code === this.formData.outlayManageSource)
|
||||
if (type) {
|
||||
this.$set(this.formData, 'outlayManageSourceName', type.name)
|
||||
}
|
||||
const identity = this.reimbursementIdentityOption.find((item) => item.code === this.formData.reimbursementIdentity)
|
||||
if (identity) {
|
||||
this.$set(this.formData, 'reimbursementIdentityName', identity.name)
|
||||
}
|
||||
},
|
||||
fillCurrentUser() {
|
||||
const user = this.$store.state.user
|
||||
this.$set(this.formData, 'userId', user.id)
|
||||
this.$set(this.formData, 'userName', user.username)
|
||||
this.$set(this.formData, 'loginName', user.loginname)
|
||||
if (user.union) {
|
||||
this.$set(this.formData, 'unionId', user.union.id)
|
||||
this.$set(this.formData, 'unionName', user.union.name)
|
||||
}
|
||||
if (user.unit) {
|
||||
this.$set(this.formData, 'unitId', user.unit.id)
|
||||
this.$set(this.formData, 'unitName', user.unit.name)
|
||||
}
|
||||
},
|
||||
formatDate(value) {
|
||||
return value ? this.$moment(value).format('YYYY-MM-DD') : '暂无'
|
||||
},
|
||||
formatRange(start, end) {
|
||||
if (!start && !end) {
|
||||
return '暂无'
|
||||
}
|
||||
return this.formatDate(start) + ' ~ ' + this.formatDate(end)
|
||||
},
|
||||
formatUser(userName, loginName) {
|
||||
if (!userName && !loginName) {
|
||||
return '暂无'
|
||||
}
|
||||
return (userName || '') + (loginName ? '(' + loginName + ')' : '')
|
||||
},
|
||||
getActivityTypeName(type) {
|
||||
const data = this.activityDeclareReimbursementList.find(function(item) {
|
||||
return item.name === type
|
||||
})
|
||||
return data ? data.typeName : type
|
||||
},
|
||||
initBudgetType() {
|
||||
return this.$businessTool.getDictOptions('ACTIVITY_BUDGET_TYPE').then((resp) => {
|
||||
const result = []
|
||||
const typeOne = resp.find((item) => item.code === 'ACTIVITY_BUDGET_TYPE_ONE')
|
||||
if (typeOne) {
|
||||
result.push(typeOne)
|
||||
}
|
||||
if (this.hasSchoolLevelRole()) {
|
||||
resp.forEach((item) => {
|
||||
if (['ACTIVITY_BUDGET_TYPE_TWO', 'ACTIVITY_BUDGET_TYPE_THREE'].includes(item.code)) {
|
||||
result.push(item)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (this.hasUnionRole()) {
|
||||
const typeTwo = resp.find((item) => item.code === 'ACTIVITY_BUDGET_TYPE_TWO')
|
||||
if (typeTwo) {
|
||||
result.push(typeTwo)
|
||||
}
|
||||
}
|
||||
if (this.hasClubRole()) {
|
||||
const typeThree = resp.find((item) => item.code === 'ACTIVITY_BUDGET_TYPE_THREE')
|
||||
if (typeThree) {
|
||||
result.push(typeThree)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.budgetTypeOption = result
|
||||
})
|
||||
},
|
||||
initReimbursementIdentity() {
|
||||
if (this.hasSchoolLevelRole()) {
|
||||
this.reimbursementIdentityOption.push({name: '校工会', code: 'school'})
|
||||
}
|
||||
if (this.hasUnionRole()) {
|
||||
this.reimbursementIdentityOption.push({name: '分工会', code: 'union'})
|
||||
}
|
||||
if (this.hasClubRole()) {
|
||||
this.reimbursementIdentityOption.push({name: '协会', code: 'club'})
|
||||
}
|
||||
},
|
||||
async init() {
|
||||
this.clubOption = await this.$businessTool.listCLubByRole()
|
||||
this.activityDeclareReimbursementList = await this.$businessTool.getEnumOptions('ActivityDeclareReimbursement')
|
||||
this.initReimbursementIdentity()
|
||||
await this.initBudgetType()
|
||||
if (this.bizId) {
|
||||
this.findOne()
|
||||
} else {
|
||||
this.fillCurrentUser()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.section-title-row {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.inline-action {
|
||||
padding: 10px 16px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,75 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<van-nav-bar :title="title" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search v-model="pageForm.searchKeyword" :show-action="false" :reverse-color="false"
|
||||
input-align="left" placeholder="请输入申请人姓名或工号搜索" @search="doSearch"></van-search>
|
||||
<van-dropdown-menu>
|
||||
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.activityReimbursementMode" :options="modeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.outlayManageSource" :options="budgetTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.budgetReimburseType" :options="budgetReimburseTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
<van-tabs v-model="pageForm.auditText" @change="onAuditChange">
|
||||
<van-tab title="已审核" name="1"></van-tab>
|
||||
<van-tab title="未审核" name="0"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<table-list :api="api" :page_form.sync="pageForm" ref="tableListRef" title="curTaskName" @ready="onReady">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="申请人工号">{{ row.loginName }}</table-column>
|
||||
<table-column label="申请人">{{ row.userName }}</table-column>
|
||||
<table-column label="报销模式">{{ row.activityReimbursementMode === 'activity' ? '活动报销' : '日常报销' }}</table-column>
|
||||
<table-column label="经费类型"><dict-tag :options="dict.type.ACTIVITY_BUDGET_TYPE" :value="row.outlayManageSource"></dict-tag></table-column>
|
||||
<table-column label="项目名称">{{ row.activityName }}</table-column>
|
||||
<table-column label="申报单位">{{ row.declareUnitName }}</table-column>
|
||||
<table-column label="实际金额">{{ row.actualMoney }}</table-column>
|
||||
<table-column label="申请时间">{{ row.applyTime }}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)"><i class="fa fa-eye"></i><span>查看</span></div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onAudit(row)"><i class="fa fa-edit"></i><span>审核</span></div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)"><i class="fa fa-undo"></i><span>撤回</span></div>
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<h5-activity-reimbursement-info ref="infoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{ formData.taskName }}</div>
|
||||
<div class="form-container">
|
||||
<van-form ref="formRef">
|
||||
<van-field label="审批意见" v-model="formData.tf_opinion" required type="textarea"
|
||||
name="tf_opinion" rows="4" autosize placeholder="请填写审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"></van-field>
|
||||
<van-field name="tf_userSign" label="签字" v-model="formData.tf_userSign" required :rules="[{ required: true, message: '请签字' }]">
|
||||
<template #input>
|
||||
<h5-signature v-model="formData.tf_userSign" slot="input"></h5-signature>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="form-actions">
|
||||
<van-button @click="handleTaskAction(6)">退回</van-button>
|
||||
<van-button type="danger" @click="handleTaskAction(2)">拒绝</van-button>
|
||||
<van-button type="primary" @click="handleTaskAction(1)">同意</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</div>
|
||||
</h5-activity-reimbursement-info>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include('../common/info.js'){}#-->
|
||||
<!--#include('../common/auditPage.js'){}#-->
|
||||
createActivityReimbursementAuditPage({
|
||||
title: '分工会审核',
|
||||
api: '/platform/activityReimbursement/branchUnion/pageData'
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,74 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<van-nav-bar :title="title" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search v-model="pageForm.searchKeyword" :show-action="false" :reverse-color="false"
|
||||
input-align="left" placeholder="请输入申请人姓名或工号搜索" @search="doSearch"></van-search>
|
||||
<van-dropdown-menu>
|
||||
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.activityReimbursementMode" :options="modeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.outlayManageSource" :options="budgetTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.budgetReimburseType" :options="budgetReimburseTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
<van-tabs v-model="pageForm.auditText" @change="onAuditChange">
|
||||
<van-tab title="已支付" name="1"></van-tab>
|
||||
<van-tab title="未支付" name="0"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<table-list :api="api" :page_form.sync="pageForm" ref="tableListRef" title="curTaskName" @ready="onReady">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="申请人工号">{{ row.loginName }}</table-column>
|
||||
<table-column label="申请人">{{ row.userName }}</table-column>
|
||||
<table-column label="报销模式">{{ row.activityReimbursementMode === 'activity' ? '活动报销' : '日常报销' }}</table-column>
|
||||
<table-column label="经费类型"><dict-tag :options="dict.type.ACTIVITY_BUDGET_TYPE" :value="row.outlayManageSource"></dict-tag></table-column>
|
||||
<table-column label="项目名称">{{ row.activityName }}</table-column>
|
||||
<table-column label="申报单位">{{ row.declareUnitName }}</table-column>
|
||||
<table-column label="实际金额">{{ row.actualMoney }}</table-column>
|
||||
<table-column label="申请时间">{{ row.applyTime }}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)"><i class="fa fa-eye"></i><span>查看</span></div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onAudit(row)"><i class="fa fa-edit"></i><span>支付</span></div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)"><i class="fa fa-undo"></i><span>撤回</span></div>
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<h5-activity-reimbursement-info ref="infoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{ formData.taskName }}</div>
|
||||
<div class="form-container">
|
||||
<van-form ref="formRef">
|
||||
<van-field label="支付意见" v-model="formData.tf_opinion" required type="textarea"
|
||||
name="tf_opinion" rows="4" autosize placeholder="请填写支付意见"
|
||||
:rules="[{ required: true, message: '请填写支付意见' }]"></van-field>
|
||||
<van-field name="tf_userSign" label="签字" v-model="formData.tf_userSign" required :rules="[{ required: true, message: '请签字' }]">
|
||||
<template #input><h5-signature v-model="formData.tf_userSign" slot="input"></h5-signature></template>
|
||||
</van-field>
|
||||
<div class="form-actions">
|
||||
<van-button @click="handleTaskAction(6)">退回</van-button>
|
||||
<van-button type="danger" @click="handleTaskAction(2)">拒绝</van-button>
|
||||
<van-button type="primary" @click="handleTaskAction(1)">确认</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</div>
|
||||
</h5-activity-reimbursement-info>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include('../common/info.js'){}#-->
|
||||
<!--#include('../common/auditPage.js'){}#-->
|
||||
createActivityReimbursementAuditPage({
|
||||
title: '出纳支付',
|
||||
api: '/platform/activityReimbursement/cashier/pageData',
|
||||
opinionMessage: '请填写支付意见'
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,73 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<van-nav-bar :title="title" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search v-model="pageForm.searchKeyword" :show-action="false" :reverse-color="false"
|
||||
input-align="left" placeholder="请输入申请人姓名或工号搜索" @search="doSearch"></van-search>
|
||||
<van-dropdown-menu>
|
||||
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.activityReimbursementMode" :options="modeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.outlayManageSource" :options="budgetTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.budgetReimburseType" :options="budgetReimburseTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
<van-tabs v-model="pageForm.auditText" @change="onAuditChange">
|
||||
<van-tab title="已审核" name="1"></van-tab>
|
||||
<van-tab title="未审核" name="0"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<table-list :api="api" :page_form.sync="pageForm" ref="tableListRef" title="curTaskName" @ready="onReady">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="申请人工号">{{ row.loginName }}</table-column>
|
||||
<table-column label="申请人">{{ row.userName }}</table-column>
|
||||
<table-column label="报销模式">{{ row.activityReimbursementMode === 'activity' ? '活动报销' : '日常报销' }}</table-column>
|
||||
<table-column label="经费类型"><dict-tag :options="dict.type.ACTIVITY_BUDGET_TYPE" :value="row.outlayManageSource"></dict-tag></table-column>
|
||||
<table-column label="项目名称">{{ row.activityName }}</table-column>
|
||||
<table-column label="申报单位">{{ row.declareUnitName }}</table-column>
|
||||
<table-column label="实际金额">{{ row.actualMoney }}</table-column>
|
||||
<table-column label="申请时间">{{ row.applyTime }}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)"><i class="fa fa-eye"></i><span>查看</span></div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onAudit(row)"><i class="fa fa-edit"></i><span>审核</span></div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)"><i class="fa fa-undo"></i><span>撤回</span></div>
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<h5-activity-reimbursement-info ref="infoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{ formData.taskName }}</div>
|
||||
<div class="form-container">
|
||||
<van-form ref="formRef">
|
||||
<van-field label="审批意见" v-model="formData.tf_opinion" required type="textarea"
|
||||
name="tf_opinion" rows="4" autosize placeholder="请填写审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"></van-field>
|
||||
<van-field name="tf_userSign" label="签字" v-model="formData.tf_userSign" required :rules="[{ required: true, message: '请签字' }]">
|
||||
<template #input><h5-signature v-model="formData.tf_userSign" slot="input"></h5-signature></template>
|
||||
</van-field>
|
||||
<div class="form-actions">
|
||||
<van-button @click="handleTaskAction(6)">退回</van-button>
|
||||
<van-button type="danger" @click="handleTaskAction(2)">拒绝</van-button>
|
||||
<van-button type="primary" @click="handleTaskAction(1)">同意</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</div>
|
||||
</h5-activity-reimbursement-info>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include('../common/info.js'){}#-->
|
||||
<!--#include('../common/auditPage.js'){}#-->
|
||||
createActivityReimbursementAuditPage({
|
||||
title: '协会负责人审核',
|
||||
api: '/platform/activityReimbursement/clubPrincipal/pageData'
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,170 @@
|
||||
function createActivityReimbursementAuditPage(options) {
|
||||
return new Vue({
|
||||
el: options.el || '#app',
|
||||
store,
|
||||
dicts: ['ACTIVITY_BUDGET_TYPE', 'ACTIVITY_BUDGET_REIMBURSE_TYPE'],
|
||||
components: {
|
||||
'h5-activity-reimbursement-info': H5_ACTIVITY_REIMBURSEMENT_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: options.title,
|
||||
api: options.api,
|
||||
reviewUrl: options.reviewUrl || '',
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
year: new Date().getFullYear(),
|
||||
audit: false,
|
||||
auditText: '0',
|
||||
searchKeyword: '',
|
||||
activityReimbursementMode: '',
|
||||
outlayManageSource: '',
|
||||
budgetReimburseType: ''
|
||||
},
|
||||
modeOptions: [
|
||||
{text: '全部报销模式', value: ''},
|
||||
{text: '活动报销', value: 'activity'},
|
||||
{text: '日常报销', value: 'daily'}
|
||||
],
|
||||
budgetTypeOptions: [{text: '全部经费类型', value: ''}],
|
||||
budgetReimburseTypeOptions: [{text: '全部预算类型', value: ''}],
|
||||
formData: {},
|
||||
showApprovalForm: false,
|
||||
opinionMessage: options.opinionMessage || '请填写审批意见'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onReady() {
|
||||
await this.initDictOptions()
|
||||
this.doSearch()
|
||||
},
|
||||
async initDictOptions() {
|
||||
const budgetTypeList = await this.getDictOptionList('ACTIVITY_BUDGET_TYPE')
|
||||
const budgetReimburseTypeList = await this.getDictOptionList('ACTIVITY_BUDGET_REIMBURSE_TYPE')
|
||||
this.$set(this, 'budgetTypeOptions', this.buildDropdownOptions('全部经费类型', budgetTypeList))
|
||||
this.$set(this, 'budgetReimburseTypeOptions', this.buildDropdownOptions('全部预算类型', budgetReimburseTypeList))
|
||||
},
|
||||
// 下拉选项依赖异步字典加载,统一兜底可避免首次进入时 Vant 下拉渲染为空。
|
||||
async getDictOptionList(dictType) {
|
||||
try {
|
||||
if (this.$businessTool && this.$businessTool.getDictOptions) {
|
||||
const data = await this.$businessTool.getDictOptions(dictType)
|
||||
return data || []
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.dict && this.dict.type && this.dict.type[dictType]) {
|
||||
return this.dict.type[dictType]
|
||||
}
|
||||
return []
|
||||
}
|
||||
if (this.dict && this.dict.type && this.dict.type[dictType]) {
|
||||
return this.dict.type[dictType]
|
||||
}
|
||||
return []
|
||||
},
|
||||
buildDropdownOptions(defaultText, list) {
|
||||
return [{text: defaultText, value: ''}].concat((list || []).map((item) => {
|
||||
return {
|
||||
text: item.name || item.label || item.text,
|
||||
value: item.code || item.value || ''
|
||||
}
|
||||
}))
|
||||
},
|
||||
onAuditChange(val) {
|
||||
this.$set(this.pageForm, 'audit', val === '1')
|
||||
this.doSearch()
|
||||
},
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
},
|
||||
onAudit(row) {
|
||||
this.showApprovalForm = true
|
||||
this.$set(this, 'formData', {})
|
||||
this.$set(this.formData, 'id', row.id)
|
||||
this.$set(this.formData, 'processTaskId', row.taskId)
|
||||
this.$set(this.formData, 'taskName', row.curTaskName)
|
||||
this.$set(this.formData, 'tf_opinion', null)
|
||||
this.$set(this.formData, 'tf_userSign', null)
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
},
|
||||
handleTaskAction(val) {
|
||||
if (!this.validateApprovalForm()) {
|
||||
return
|
||||
}
|
||||
this.$refs.formRef.validate().then(() => {
|
||||
this.$dialog.confirm({
|
||||
title: '温馨提示',
|
||||
message: '您确定要提交吗?'
|
||||
}).then(() => {
|
||||
this.submitReview(val)
|
||||
}).catch(() => {})
|
||||
}).catch(() => {})
|
||||
},
|
||||
// 自定义签字组件在 van-field 插槽中,需要先按真实表单值做提交前校验。
|
||||
validateApprovalForm() {
|
||||
if (!this.formData.tf_opinion) {
|
||||
this.$toast.fail(this.opinionMessage)
|
||||
return false
|
||||
}
|
||||
if (!this.formData.tf_userSign) {
|
||||
this.$toast.fail('请签字')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
submitReview(val) {
|
||||
const data = {
|
||||
id: this.formData.id,
|
||||
processTaskId: this.formData.processTaskId,
|
||||
tf_opinion: this.formData.tf_opinion,
|
||||
tf_userSign: this.formData.tf_userSign,
|
||||
submitType: val
|
||||
}
|
||||
if (this.reviewUrl) {
|
||||
this.$axios.post(this.reviewUrl, {
|
||||
data: JSON.stringify(data),
|
||||
id: this.formData.id
|
||||
}).then(this.afterSubmit)
|
||||
} else {
|
||||
this.$axios.post('/flow/common/executeTask', {
|
||||
data: JSON.stringify(data)
|
||||
}).then(this.afterSubmit)
|
||||
}
|
||||
},
|
||||
afterSubmit(res) {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success(res.msg)
|
||||
this.$refs.infoRef.onClose()
|
||||
this.doSearch()
|
||||
} else {
|
||||
this.$toast.fail(res.msg)
|
||||
}
|
||||
},
|
||||
onRevoke(row) {
|
||||
this.$dialog.confirm({
|
||||
title: '温馨提示',
|
||||
message: '您确定要撤回吗?'
|
||||
}).then(() => {
|
||||
this.$axios.post('/flow/common/revokeTask', {taskId: row.taskId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
} else {
|
||||
this.$toast.fail(res.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
doSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.$set(this.pageForm, 'pageNumber', 1)
|
||||
this.$set(this.pageForm, 'totalCount', 0)
|
||||
this.$refs.tableListRef.doSearch()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
const H5_ACTIVITY_REIMBURSEMENT_INFO = {
|
||||
template: [
|
||||
'<van-action-sheet v-model="visible" title="查看详情">',
|
||||
' <div class="detail-container activity-reimbursement-detail">',
|
||||
' <van-cell-group title="基本信息">',
|
||||
' <van-cell title="报销模式">{{ viewData.activityReimbursementMode === "activity" ? "活动报销" : "日常报销" }}</van-cell>',
|
||||
' <van-cell title="经费类型">',
|
||||
' <dict-tag :options="dict.type.ACTIVITY_BUDGET_TYPE" :value="viewData.outlayManageSource"></dict-tag>',
|
||||
' </van-cell>',
|
||||
' <van-cell title="扣款预算">{{ viewData.deductionBudgetName || "暂无" }}</van-cell>',
|
||||
' <van-cell title="项目名称">{{ viewData.activityName || "暂无" }}</van-cell>',
|
||||
' <van-cell title="申请人">{{ formatUser(viewData.userName, viewData.loginName) }}</van-cell>',
|
||||
' <van-cell :title="viewData.outlayManageSource === \'ACTIVITY_BUDGET_TYPE_THREE\' ? \'申请协会\' : \'申请单位\'">{{ viewData.declareUnitName || "暂无" }}</van-cell>',
|
||||
' <van-cell title="申请时间">{{ viewData.applyTime || "暂无" }}</van-cell>',
|
||||
' <template v-if="viewData.activityReimbursementMode === \'activity\'">',
|
||||
' <van-cell title="活动计划时间">{{ formatRange(viewData.planStartTime, viewData.planEndTime) }}</van-cell>',
|
||||
' <van-cell title="实际活动时间">{{ formatRange(viewData.startTime, viewData.endTime) }}</van-cell>',
|
||||
' <van-cell title="活动人数">{{ viewData.activityNumber || "暂无" }}</van-cell>',
|
||||
' <van-cell class="direction-column-cell" title="活动地址">{{ viewData.activityAddress || "暂无" }}</van-cell>',
|
||||
' </template>',
|
||||
' </van-cell-group>',
|
||||
' <van-cell-group title="费用">',
|
||||
' <van-cell v-for="(item,index) in viewData.budgets" :key="index" class="direction-column-cell" :title="\'费用\' + (index + 1)">',
|
||||
' <div class="fee-row">费用项目名称:{{ item.name || "暂无" }}</div>',
|
||||
' <div class="fee-row" v-if="viewData.activityReimbursementMode === \'activity\'">预算费用:{{ item.budgetPrice || 0 }}</div>',
|
||||
' <div class="fee-row">实际费用:{{ item.actualPrice || 0 }}</div>',
|
||||
' <div class="fee-row">收款人:{{ item.username || "暂无" }}</div>',
|
||||
' <div class="fee-row">支行名称:{{ item.bankName || "暂无" }}</div>',
|
||||
' <div class="fee-row">报销卡号:{{ item.bankCardNum || "暂无" }}</div>',
|
||||
' <div class="fee-row">备注:{{ item.remark || "暂无" }}</div>',
|
||||
' </van-cell>',
|
||||
' <van-cell title="合计">{{ totalActualMoney }} 元</van-cell>',
|
||||
' </van-cell-group>',
|
||||
' <van-cell-group title="附件">',
|
||||
' <van-cell class="direction-column-cell" title="发票等">',
|
||||
' <file-preview v-if="viewData.billFiles && viewData.billFiles.length > 0" :files="viewData.billFiles" complete_result></file-preview>',
|
||||
' <span v-else>暂无</span>',
|
||||
' </van-cell>',
|
||||
' <template v-if="viewData.activityReimbursementMode === \'activity\'">',
|
||||
' <van-cell class="direction-column-cell" title="活动报道照片/总结/链接">',
|
||||
' <file-preview v-if="viewData.photoFiles && viewData.photoFiles.length > 0" :files="viewData.photoFiles" complete_result></file-preview>',
|
||||
' <span v-else>暂无</span>',
|
||||
' </van-cell>',
|
||||
' <van-cell class="direction-column-cell" title="奖品/纪念品/服装/道具等活动人员名单">',
|
||||
' <file-preview v-if="viewData.otherFiles && viewData.otherFiles.length > 0" :files="viewData.otherFiles" complete_result></file-preview>',
|
||||
' <span v-else>暂无</span>',
|
||||
' </van-cell>',
|
||||
' </template>',
|
||||
' <van-cell class="direction-column-cell" title="签字">',
|
||||
' <van-image v-if="viewData.sign" :src="viewData.sign" class="signature-image"></van-image>',
|
||||
' <span v-else>暂无签字</span>',
|
||||
' </van-cell>',
|
||||
' </van-cell-group>',
|
||||
' <template v-for="task in doneTasks">',
|
||||
' <van-cell-group :title="task.displayName" v-if="task.ext && task.ext.isFirstTaskNode">',
|
||||
' <van-cell title="申请用户">{{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})</van-cell>',
|
||||
' <van-cell title="申请时间">{{ task.finishTime }}</van-cell>',
|
||||
' <van-cell title="办理结果">',
|
||||
' <dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>',
|
||||
' </van-cell>',
|
||||
' </van-cell-group>',
|
||||
' <van-cell-group :title="task.displayName" v-else>',
|
||||
' <van-cell title="办理用户">{{ formatTaskUser(task) }}</van-cell>',
|
||||
' <van-cell title="办理时间">{{ task.finishTime }}</van-cell>',
|
||||
' <van-cell title="办理结果">',
|
||||
' <dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext && task.ext.submitType"></dict-tag>',
|
||||
' </van-cell>',
|
||||
' <van-cell title="办理意见" class="direction-column-cell">',
|
||||
' <div v-html="task.taskFormData && (task.taskFormData.tf_opinion || task.taskFormData.opinion)"></div>',
|
||||
' </van-cell>',
|
||||
' <van-cell title="签字" class="direction-column-cell">',
|
||||
' <van-image v-if="task.ext && task.ext.tf_userSign" :src="task.ext.tf_userSign" class="signature-image"></van-image>',
|
||||
' <span v-else>暂无</span>',
|
||||
' </van-cell>',
|
||||
' </van-cell-group>',
|
||||
' </template>',
|
||||
' </div>',
|
||||
' <slot></slot>',
|
||||
'</van-action-sheet>'
|
||||
].join(''),
|
||||
store,
|
||||
dicts: ['PROCESS_TASK_SUBMIT_TYPE', 'ACTIVITY_BUDGET_TYPE'],
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
viewData: {},
|
||||
doneTasks: [],
|
||||
row: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalActualMoney() {
|
||||
if (!this.viewData.budgets || this.viewData.budgets.length === 0) {
|
||||
return '0.00'
|
||||
}
|
||||
const sum = this.viewData.budgets.reduce((total, item) => {
|
||||
return total + Number(item.actualPrice || 0)
|
||||
}, 0)
|
||||
return sum.toFixed(2)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(row) {
|
||||
this.row = row
|
||||
this.visible = true
|
||||
this.getInfo()
|
||||
this.getDoneTasks()
|
||||
},
|
||||
onClose() {
|
||||
this.visible = false
|
||||
},
|
||||
getInfo() {
|
||||
this.$axios.post('/platform/activityReimbursement/mine/findOne', {id: this.row.id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.viewData = this.normalizeInfo(res.data || {})
|
||||
}
|
||||
})
|
||||
},
|
||||
getDoneTasks() {
|
||||
this.$axios.post('/flow/common/doneTasks', {bizId: this.row.id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.doneTasks = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
normalizeInfo(data) {
|
||||
data.budgets = this.parseJsonArray(data.budgets)
|
||||
data.billFiles = this.parseJsonArray(data.billFiles)
|
||||
data.photoFiles = this.parseJsonArray(data.photoFiles)
|
||||
data.otherFiles = this.parseJsonArray(data.otherFiles)
|
||||
return data
|
||||
},
|
||||
parseJsonArray(value) {
|
||||
if (!value) {
|
||||
return []
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
formatDate(value) {
|
||||
return value ? this.$moment(value).format('YYYY-MM-DD') : '未填写'
|
||||
},
|
||||
formatRange(start, end) {
|
||||
return this.formatDate(start) + ' ~ ' + this.formatDate(end)
|
||||
},
|
||||
formatUser(userName, loginName) {
|
||||
if (!userName && !loginName) {
|
||||
return '暂无'
|
||||
}
|
||||
return (userName || '') + (loginName ? '(' + loginName + ')' : '')
|
||||
},
|
||||
formatTaskUser(task) {
|
||||
if (!task || !task.taskFormData) {
|
||||
return '暂无'
|
||||
}
|
||||
const name = task.taskFormData.tf_userName || task.taskFormData.userName || ''
|
||||
const account = task.taskFormData.loginName || ''
|
||||
return this.formatUser(name, account)
|
||||
}
|
||||
},
|
||||
style: [
|
||||
'.activity-reimbursement-detail .fee-row{line-height:24px;}'
|
||||
].join('')
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<van-nav-bar :title="title" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search v-model="pageForm.searchKeyword" :show-action="false" :reverse-color="false"
|
||||
input-align="left" placeholder="请输入申请人姓名或工号搜索" @search="doSearch"></van-search>
|
||||
<van-dropdown-menu>
|
||||
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.activityReimbursementMode" :options="modeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.outlayManageSource" :options="budgetTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.budgetReimburseType" :options="budgetReimburseTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
<van-tabs v-model="pageForm.auditText" @change="onAuditChange">
|
||||
<van-tab title="已审核" name="1"></van-tab>
|
||||
<van-tab title="未审核" name="0"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<table-list :api="api" :page_form.sync="pageForm" ref="tableListRef" title="curTaskName" @ready="onReady">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="申请人工号">{{ row.loginName }}</table-column>
|
||||
<table-column label="申请人">{{ row.userName }}</table-column>
|
||||
<table-column label="报销模式">{{ row.activityReimbursementMode === 'activity' ? '活动报销' : '日常报销' }}</table-column>
|
||||
<table-column label="经费类型"><dict-tag :options="dict.type.ACTIVITY_BUDGET_TYPE" :value="row.outlayManageSource"></dict-tag></table-column>
|
||||
<table-column label="项目名称">{{ row.activityName }}</table-column>
|
||||
<table-column label="申报单位">{{ row.declareUnitName }}</table-column>
|
||||
<table-column label="实际金额">{{ row.actualMoney }}</table-column>
|
||||
<table-column label="申请时间">{{ row.applyTime }}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)"><i class="fa fa-eye"></i><span>查看</span></div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onAudit(row)"><i class="fa fa-edit"></i><span>审核</span></div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)"><i class="fa fa-undo"></i><span>撤回</span></div>
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<h5-activity-reimbursement-info ref="infoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{ formData.taskName }}</div>
|
||||
<div class="form-container">
|
||||
<van-form ref="formRef">
|
||||
<van-field label="审批意见" v-model="formData.tf_opinion" required type="textarea"
|
||||
name="tf_opinion" rows="4" autosize placeholder="请填写审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"></van-field>
|
||||
<van-field name="tf_userSign" label="签字" v-model="formData.tf_userSign" required :rules="[{ required: true, message: '请签字' }]">
|
||||
<template #input><h5-signature v-model="formData.tf_userSign" slot="input"></h5-signature></template>
|
||||
</van-field>
|
||||
<div class="form-actions">
|
||||
<van-button @click="handleTaskAction(6)">退回</van-button>
|
||||
<van-button type="danger" @click="handleTaskAction(2)">拒绝</van-button>
|
||||
<van-button type="primary" @click="handleTaskAction(1)">同意</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</div>
|
||||
</h5-activity-reimbursement-info>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include('../common/info.js'){}#-->
|
||||
<!--#include('../common/auditPage.js'){}#-->
|
||||
createActivityReimbursementAuditPage({
|
||||
title: '校工会副主席审核',
|
||||
api: '/platform/activityReimbursement/deputySchoolUnion/pageData',
|
||||
reviewUrl: '/platform/activityReimbursement/deputySchoolUnion/doReview'
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,130 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<van-nav-bar title="我的申请" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search v-model="pageForm.searchKeyword" :show-action="false" :reverse-color="false"
|
||||
input-align="left" placeholder="请输入申请人姓名或工号搜索" @search="doSearch"></van-search>
|
||||
<van-dropdown-menu>
|
||||
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</van-sticky>
|
||||
|
||||
<table-list api="/platform/activityReimbursement/mine/pageData" :page_form.sync="pageForm"
|
||||
ref="tableListRef" title="taskName" @ready="doSearch">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="申请人工号">{{ row.loginName }}</table-column>
|
||||
<table-column label="申请人">{{ row.userName }}</table-column>
|
||||
<table-column label="报销模式">{{ row.activityReimbursementMode === 'activity' ? '活动报销' : '日常报销' }}</table-column>
|
||||
<table-column label="经费类型">
|
||||
<dict-tag :options="dict.type.ACTIVITY_BUDGET_TYPE" :value="row.outlayManageSource"></dict-tag>
|
||||
</table-column>
|
||||
<table-column label="项目名称">{{ row.activityName }}</table-column>
|
||||
<table-column label="申报单位">{{ row.declareUnitName }}</table-column>
|
||||
<table-column label="实际金额">{{ row.actualMoney }}</table-column>
|
||||
<table-column label="申请时间">{{ row.applyTime }}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
<div class="action-btn" @click="onEdit(row)" v-if="row.taskKey === 'startTask' || !row.instanceId">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>编辑</span>
|
||||
</div>
|
||||
<div class="action-btn" @click="doExportReimbursement(row)" v-if="row.instanceState === 20">
|
||||
<i class="fa fa-download"></i>
|
||||
<span>凭证</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
|
||||
<i class="fa fa-undo"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
<div class="action-btn delete" @click="onDelete(row)" v-if="row.taskKey === 'startTask' || !row.instanceId">
|
||||
<i class="fa fa-trash"></i>
|
||||
<span>删除</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<h5-activity-reimbursement-info ref="infoRef"></h5-activity-reimbursement-info>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include('../common/info.js'){}#-->
|
||||
new Vue({
|
||||
el: '#app',
|
||||
store,
|
||||
dicts: ['ACTIVITY_BUDGET_TYPE'],
|
||||
components: {
|
||||
'h5-activity-reimbursement-info': H5_ACTIVITY_REIMBURSEMENT_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
year: new Date().getFullYear(),
|
||||
searchKeyword: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onView(row) {
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
},
|
||||
onEdit(row) {
|
||||
this.$pjaxReplace('/platform/activityReimbursement/apply/h5?taskId=' + (row.startTaskId || '') + '&bizId=' + row.id)
|
||||
},
|
||||
doExportReimbursement(row) {
|
||||
window.location.href = '/platform/activityReimbursement/mine/doExportReimbursement?id=' + row.id
|
||||
},
|
||||
onDelete(row) {
|
||||
this.$dialog.confirm({
|
||||
title: '温馨提示',
|
||||
message: '您确定要删除吗?'
|
||||
}).then(() => {
|
||||
this.$axios.post('/platform/activityReimbursement/mine/onDelete', {id: row.id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
} else {
|
||||
this.$toast.fail(res.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
onRevoke(row) {
|
||||
this.$dialog.confirm({
|
||||
title: '温馨提示',
|
||||
message: '您确定要撤回吗?'
|
||||
}).then(() => {
|
||||
this.$axios.post('/flow/common/revokeTask', {taskId: row.startTaskId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success(res.msg)
|
||||
this.doSearch()
|
||||
} else {
|
||||
this.$toast.fail(res.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
doSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.$set(this.pageForm, 'pageNumber', 1)
|
||||
this.$set(this.pageForm, 'totalCount', 0)
|
||||
this.$refs.tableListRef.doSearch()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,73 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<van-nav-bar :title="title" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search v-model="pageForm.searchKeyword" :show-action="false" :reverse-color="false"
|
||||
input-align="left" placeholder="请输入申请人姓名或工号搜索" @search="doSearch"></van-search>
|
||||
<van-dropdown-menu>
|
||||
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.activityReimbursementMode" :options="modeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.outlayManageSource" :options="budgetTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.budgetReimburseType" :options="budgetReimburseTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
<van-tabs v-model="pageForm.auditText" @change="onAuditChange">
|
||||
<van-tab title="已审核" name="1"></van-tab>
|
||||
<van-tab title="未审核" name="0"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<table-list :api="api" :page_form.sync="pageForm" ref="tableListRef" title="curTaskName" @ready="onReady">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="申请人工号">{{ row.loginName }}</table-column>
|
||||
<table-column label="申请人">{{ row.userName }}</table-column>
|
||||
<table-column label="报销模式">{{ row.activityReimbursementMode === 'activity' ? '活动报销' : '日常报销' }}</table-column>
|
||||
<table-column label="经费类型"><dict-tag :options="dict.type.ACTIVITY_BUDGET_TYPE" :value="row.outlayManageSource"></dict-tag></table-column>
|
||||
<table-column label="项目名称">{{ row.activityName }}</table-column>
|
||||
<table-column label="申报单位">{{ row.declareUnitName }}</table-column>
|
||||
<table-column label="实际金额">{{ row.actualMoney }}</table-column>
|
||||
<table-column label="申请时间">{{ row.applyTime }}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)"><i class="fa fa-eye"></i><span>查看</span></div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onAudit(row)"><i class="fa fa-edit"></i><span>审核</span></div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)"><i class="fa fa-undo"></i><span>撤回</span></div>
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<h5-activity-reimbursement-info ref="infoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{ formData.taskName }}</div>
|
||||
<div class="form-container">
|
||||
<van-form ref="formRef">
|
||||
<van-field label="审批意见" v-model="formData.tf_opinion" required type="textarea"
|
||||
name="tf_opinion" rows="4" autosize placeholder="请填写审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"></van-field>
|
||||
<van-field name="tf_userSign" label="签字" v-model="formData.tf_userSign" required :rules="[{ required: true, message: '请签字' }]">
|
||||
<template #input><h5-signature v-model="formData.tf_userSign" slot="input"></h5-signature></template>
|
||||
</van-field>
|
||||
<div class="form-actions">
|
||||
<van-button @click="handleTaskAction(6)">退回</van-button>
|
||||
<van-button type="danger" @click="handleTaskAction(2)">拒绝</van-button>
|
||||
<van-button type="primary" @click="handleTaskAction(1)">同意</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</div>
|
||||
</h5-activity-reimbursement-info>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include('../common/info.js'){}#-->
|
||||
<!--#include('../common/auditPage.js'){}#-->
|
||||
createActivityReimbursementAuditPage({
|
||||
title: '校工会财务审核',
|
||||
api: '/platform/activityReimbursement/schoolFinance/pageData'
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,73 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<van-nav-bar :title="title" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search v-model="pageForm.searchKeyword" :show-action="false" :reverse-color="false"
|
||||
input-align="left" placeholder="请输入申请人姓名或工号搜索" @search="doSearch"></van-search>
|
||||
<van-dropdown-menu>
|
||||
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.activityReimbursementMode" :options="modeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.outlayManageSource" :options="budgetTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.budgetReimburseType" :options="budgetReimburseTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
<van-tabs v-model="pageForm.auditText" @change="onAuditChange">
|
||||
<van-tab title="已审核" name="1"></van-tab>
|
||||
<van-tab title="未审核" name="0"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<table-list :api="api" :page_form.sync="pageForm" ref="tableListRef" title="curTaskName" @ready="onReady">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="申请人工号">{{ row.loginName }}</table-column>
|
||||
<table-column label="申请人">{{ row.userName }}</table-column>
|
||||
<table-column label="报销模式">{{ row.activityReimbursementMode === 'activity' ? '活动报销' : '日常报销' }}</table-column>
|
||||
<table-column label="经费类型"><dict-tag :options="dict.type.ACTIVITY_BUDGET_TYPE" :value="row.outlayManageSource"></dict-tag></table-column>
|
||||
<table-column label="项目名称">{{ row.activityName }}</table-column>
|
||||
<table-column label="申报单位">{{ row.declareUnitName }}</table-column>
|
||||
<table-column label="实际金额">{{ row.actualMoney }}</table-column>
|
||||
<table-column label="申请时间">{{ row.applyTime }}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)"><i class="fa fa-eye"></i><span>查看</span></div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onAudit(row)"><i class="fa fa-edit"></i><span>审核</span></div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)"><i class="fa fa-undo"></i><span>撤回</span></div>
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<h5-activity-reimbursement-info ref="infoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{ formData.taskName }}</div>
|
||||
<div class="form-container">
|
||||
<van-form ref="formRef">
|
||||
<van-field label="审批意见" v-model="formData.tf_opinion" required type="textarea"
|
||||
name="tf_opinion" rows="4" autosize placeholder="请填写审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"></van-field>
|
||||
<van-field name="tf_userSign" label="签字" v-model="formData.tf_userSign" required :rules="[{ required: true, message: '请签字' }]">
|
||||
<template #input><h5-signature v-model="formData.tf_userSign" slot="input"></h5-signature></template>
|
||||
</van-field>
|
||||
<div class="form-actions">
|
||||
<van-button @click="handleTaskAction(6)">退回</van-button>
|
||||
<van-button type="danger" @click="handleTaskAction(2)">拒绝</van-button>
|
||||
<van-button type="primary" @click="handleTaskAction(1)">同意</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</div>
|
||||
</h5-activity-reimbursement-info>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include('../common/info.js'){}#-->
|
||||
<!--#include('../common/auditPage.js'){}#-->
|
||||
createActivityReimbursementAuditPage({
|
||||
title: '校工会主席审核',
|
||||
api: '/platform/activityReimbursement/schoolPrincipal/pageData'
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,74 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<van-nav-bar :title="title" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
<van-sticky offset-top="46px">
|
||||
<van-search v-model="pageForm.searchKeyword" :show-action="false" :reverse-color="false"
|
||||
input-align="left" placeholder="请输入申请人姓名或工号搜索" @search="doSearch"></van-search>
|
||||
<van-dropdown-menu>
|
||||
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.activityReimbursementMode" :options="modeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.outlayManageSource" :options="budgetTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.budgetReimburseType" :options="budgetReimburseTypeOptions" @change="doSearch"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
<van-tabs v-model="pageForm.auditText" @change="onAuditChange">
|
||||
<van-tab title="已审核" name="1"></van-tab>
|
||||
<van-tab title="未审核" name="0"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<table-list :api="api" :page_form.sync="pageForm" ref="tableListRef" title="curTaskName" @ready="onReady">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="申请人工号">{{ row.loginName }}</table-column>
|
||||
<table-column label="申请人">{{ row.userName }}</table-column>
|
||||
<table-column label="报销模式">{{ row.activityReimbursementMode === 'activity' ? '活动报销' : '日常报销' }}</table-column>
|
||||
<table-column label="经费类型"><dict-tag :options="dict.type.ACTIVITY_BUDGET_TYPE" :value="row.outlayManageSource"></dict-tag></table-column>
|
||||
<table-column label="项目名称">{{ row.activityName }}</table-column>
|
||||
<table-column label="申报单位">{{ row.declareUnitName }}</table-column>
|
||||
<table-column label="实际金额">{{ row.actualMoney }}</table-column>
|
||||
<table-column label="申请时间">{{ row.applyTime }}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)"><i class="fa fa-eye"></i><span>查看</span></div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onAudit(row)"><i class="fa fa-edit"></i><span>审核</span></div>
|
||||
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)"><i class="fa fa-undo"></i><span>撤回</span></div>
|
||||
</template>
|
||||
</table-list>
|
||||
|
||||
<h5-activity-reimbursement-info ref="infoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">{{ formData.taskName }}</div>
|
||||
<div class="form-container">
|
||||
<van-form ref="formRef">
|
||||
<van-field label="审批意见" v-model="formData.tf_opinion" required type="textarea"
|
||||
name="tf_opinion" rows="4" autosize placeholder="请填写审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"></van-field>
|
||||
<van-field name="tf_userSign" label="签字" v-model="formData.tf_userSign" required :rules="[{ required: true, message: '请签字' }]">
|
||||
<template #input><h5-signature v-model="formData.tf_userSign" slot="input"></h5-signature></template>
|
||||
</van-field>
|
||||
<div class="form-actions">
|
||||
<van-button @click="handleTaskAction(6)">退回</van-button>
|
||||
<van-button type="danger" @click="handleTaskAction(2)">拒绝</van-button>
|
||||
<van-button type="primary" @click="handleTaskAction(1)">同意</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
</div>
|
||||
</div>
|
||||
</h5-activity-reimbursement-info>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include('../common/info.js'){}#-->
|
||||
<!--#include('../common/auditPage.js'){}#-->
|
||||
createActivityReimbursementAuditPage({
|
||||
title: '校工会预审',
|
||||
api: '/platform/activityReimbursement/schoolUnion/pageData',
|
||||
reviewUrl: '/platform/activityReimbursement/schoolUnion/doReview'
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||