This commit is contained in:
zhouhefeng
2026-05-28 18:31:36 +08:00
parent daac940f3f
commit 7ee1c4fbb5
112 changed files with 9837 additions and 273 deletions
@@ -9,6 +9,8 @@ import com.budwk.app.sys.models.Sys_config;
import com.budwk.app.sys.services.SysConfigService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.integration.jedis.pubsub.PubSubService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -32,6 +34,8 @@ public class SysConfController {
private SysConfigService sysConfigService;
@Inject
private PubSubService pubSubService;
@Inject
private Dao dao;
@At("")
@Ok("beetl:/platform/sys/conf/index.html")
@@ -40,6 +44,18 @@ public class SysConfController {
}
private void ensureAppImageConfig(String configKey, String note) {
if (sysConfigService.fetch(configKey) != null) {
return;
}
Sys_config conf = new Sys_config();
conf.setConfigKey(configKey);
conf.setConfigValue("");
conf.setNote(note);
conf.setCreatedBy(SecurityUtil.getUserId());
sysConfigService.insert(conf);
}
@At
@Ok("json")
@SaCheckPermission("sys.manager.conf.add")
@@ -73,13 +89,31 @@ public class SysConfController {
@SLog(tag = "修改参数", msg = "${conf.configKey}:${conf.configValue}")
public Object editDo(@Param("..") Sys_config conf) {
try {
ensureConfigValueColumn(conf);
conf.setUpdatedBy(SecurityUtil.getUserId());
if (sysConfigService.updateIgnoreNull(conf) > 0) {
pubSubService.fire(RedisConstant.PLATFORM_REDIS_PREFIX + "web:platform", "sys_config");
}
return Result.success();
} catch (Exception e) {
return Result.error();
log.error("Update sys config failed: " + (conf == null ? "" : conf.getConfigKey()), e);
return Result.error("save failed: " + e.getMessage());
}
}
private void ensureConfigValueColumn(Sys_config conf) {
if (conf == null || (!"AppHomeImg".equals(conf.getConfigKey())
&& !"AppFeaturedActivityImg".equals(conf.getConfigKey())
&& !"AppFestivalBenefitImg".equals(conf.getConfigKey()))) {
return;
}
String configValue = Strings.sNull(conf.getConfigValue());
if (configValue.length() <= 100) {
return;
}
String dbType = Strings.sNull(dao.getJdbcExpert().getDatabaseType()).toLowerCase();
if (dbType.contains("mysql")) {
dao.execute(Sqls.create("ALTER TABLE `sys_config` MODIFY COLUMN `configValue` TEXT"));
}
}
@@ -106,6 +140,8 @@ public class SysConfController {
@SaCheckPermission("sys.manager.conf")
public Object data(@Param("pageNumber") int pageNumber, @Param("pageSize") int pageSize, @Param("pageOrderName") String pageOrderName, @Param("pageOrderBy") String pageOrderBy) {
try {
ensureAppImageConfig("AppFeaturedActivityImg", "精彩活动页顶部图片");
ensureAppImageConfig("AppFestivalBenefitImg", "节日福利页顶部图片");
Cnd cnd = Cnd.NEW();
if (Strings.isNotBlank(pageOrderName) && Strings.isNotBlank(pageOrderBy)) {
cnd.orderBy(pageOrderName, PageUtil.getOrder(pageOrderBy));
@@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import io.swagger.annotations.Api;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
@@ -32,6 +32,8 @@ public class SysH5IndexController {
@Inject
private Dao dao;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
@At
@Ok("beetl:/platform/zhghh5/sys/home/index.html")
@@ -40,6 +42,20 @@ public class SysH5IndexController {
}
@At
@Ok("beetl:/platform/zhghh5/sys/home/featuredActivity.html")
@SaCheckLogin
public void featuredActivity() {
}
@At
@Ok("beetl:/platform/zhghh5/sys/home/festivalBenefit.html")
@SaCheckLogin
public void festivalBenefit() {
}
@At
@Ok("beetl:/platform/zhghh5/index/mine/index.html")
@SaCheckLogin
@@ -88,8 +104,7 @@ public class SysH5IndexController {
}
if (allowUserGroupId != null) {
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", allowUserGroupId).and("userId", "=", userId));
if (count > 0) {
if (activityBasicScopeService.isUserInGroup(allowUserGroupId, userId)) {
allowActivityList.add(activity);
continue;
}
@@ -97,7 +112,9 @@ public class SysH5IndexController {
if (StrUtil.isNotBlank(allowUserSql)){
Sql sql = Sqls.create(allowUserSql).setParam("userId", userId);
NutMap nutMap = (NutMap) Daos.query(dao, sql.toString(), Sqls.callback.map());
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
NutMap nutMap = (NutMap) sql.getResult();
if (Lang.isNotEmpty(nutMap) && StrUtil.isNotBlank(nutMap.getString("userId"))) {
allowActivityList.add(activity);
}
@@ -6,13 +6,16 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.sys.models.Sys_menu;
import com.budwk.app.sys.services.SysMenuService;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import io.swagger.annotations.ApiOperation;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
@@ -56,6 +59,8 @@ public class SysHomeController {
private SysUserService sysUserService;
@Inject
private RedisService redisService;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
@At("")
@Ok("re")
@@ -223,8 +228,7 @@ public class SysHomeController {
}
if (allowUserGroupId != null) {
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", allowUserGroupId).and("userId", "=", userId));
if (count > 0) {
if (activityBasicScopeService.isUserInGroup(allowUserGroupId, userId)) {
allowActivityList.add(activity);
continue;
}
@@ -232,7 +236,9 @@ public class SysHomeController {
if (StrUtil.isNotBlank(allowUserSql)) {
Sql sql = Sqls.create(allowUserSql).setParam("userId", userId);
NutMap nutMap = (NutMap) Daos.query(dao, sql.toString(), Sqls.callback.map());
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
NutMap nutMap = (NutMap) sql.getResult();
if (Lang.isNotEmpty(nutMap) && StrUtil.isNotBlank(nutMap.getString("userId"))) {
allowActivityList.add(activity);
}
@@ -257,6 +263,20 @@ public class SysHomeController {
// return Result.success(hasPermisiionList);
}
@At
@SaCheckLogin
@ApiOperation("首页工作模板")
@Ok("json")
public Result listHomeTemplate() {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.success(Collections.emptyList());
}
FieldFilter fieldFilter = FieldFilter.locked(Sys_home_template.class, "allowUserSql|classPath");
List<Sys_home_template> list = Daos.ext(dao, fieldFilter).query(Sys_home_template.class,
Cnd.NEW().desc("top").desc("sortNo"));
return Result.success(list);
}
@At
@SaCheckLogin
@@ -340,8 +340,11 @@ public class SysUserController {
@At
@Ok("json")
@SaCheckLogin
public Result subAppMenus(@Param("appId") String appId) {
public Result subAppMenus(@Param("appId") String appId, @Param("platform") String platform) {
List<Sys_menu> menus = sysUserService.getMenus(SecurityUtil.getUserId());
if (StrUtil.isNotBlank(platform)) {
menus = menus.stream().filter(menu -> platform.equals(menu.getPlatform())).toList();
}
List<Sys_menu> list = SysMenuUtil.createTreeMenus(menus, appId);
if (ObjectUtil.isEmpty(list)) {
List<Sys_menu> self = menus.stream().filter(menu -> menu.getId().equals(appId)).toList();
@@ -0,0 +1,107 @@
package com.budwk.app.sys.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.sys.param.SysHomeTemplatePageForm;
import com.budwk.app.sys.services.SysHomeTemplateService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import javax.validation.Valid;
@IocBean
@At("/platform/sys/worktemplate")
@Ok("json:full")
@Api(value = "首页工作模板")
public class SysWorkTemplateController {
@Inject
private SysHomeTemplateService sysHomeTemplateService;
@At("")
@Ok("beetl:/platform/sys/worktemplate/index.html")
@SaCheckPermission("sys.worktemplate")
public void index() {
}
@At
@SaCheckPermission("sys.worktemplate")
@ApiOperation("分页数据")
public Result pageData(@Valid SysHomeTemplatePageForm pageForm) {
Cnd cnd = Cnd.NEW();
cnd.and(Cnd.likeEX(Sys_home_template::getName, pageForm.getName()));
cnd.desc(Sys_home_template::getSortNo);
Pagination pagination = sysHomeTemplateService.listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
return Result.success(pagination);
}
@At
@SaCheckPermission("sys.worktemplate")
@ApiOperation("启用")
public Result enable(@Valid String id) {
sysHomeTemplateService.update(Chain.make("enable", 1), Cnd.where("id", "=", id));
return Result.success();
}
@At
@SaCheckPermission("sys.worktemplate")
@ApiOperation("关闭")
public Result disable(@Valid String id) {
sysHomeTemplateService.update(Chain.make("enable", 0), Cnd.where("id", "=", id));
return Result.success();
}
@At
@SaCheckPermission("sys.worktemplate")
@ApiOperation("修改模板名称")
public Result updateTemplateName(@Valid String id, String templateName) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
sysHomeTemplateService.update(Chain.make("templateName", templateName), Cnd.where("id", "=", id));
return Result.success();
}
@At
@SaCheckPermission("sys.worktemplate")
@ApiOperation("修改模板图标")
public Result updateTemplateIcon(@Valid String id, String templateIcon) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
sysHomeTemplateService.update(Chain.make("templateIcon", templateIcon), Cnd.where("id", "=", id));
return Result.success();
}
@At
@SaCheckPermission("sys.worktemplate")
@ApiOperation("置顶")
public Result topUp(@Valid String id) {
Sys_home_template sysHomeTemplate = new Sys_home_template();
sysHomeTemplate.setId(id);
sysHomeTemplate.setTop(true);
sysHomeTemplateService.updateIgnoreNull(sysHomeTemplate);
return Result.success();
}
@At
@SaCheckPermission("sys.worktemplate")
@ApiOperation("取消置顶")
public Result cancelTopUp(@Valid String id) {
Sys_home_template sysHomeTemplate = new Sys_home_template();
sysHomeTemplate.setId(id);
sysHomeTemplate.setTop(false);
sysHomeTemplateService.updateIgnoreNull(sysHomeTemplate);
return Result.success();
}
}
@@ -22,7 +22,7 @@ public class Sys_config extends BaseModel implements Serializable {
private String configKey;
@Column
@ColDefine(type = ColType.VARCHAR, width = 100)
@ColDefine(type = ColType.TEXT)
private String configValue;
@Column
@@ -0,0 +1,104 @@
package com.budwk.app.sys.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import java.util.Date;
@EqualsAndHashCode(callSuper = true)
@Data
@Table
@Comment("首页工作模板")
public class Sys_home_template extends BaseModel {
@Name
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("主键ID,和业务ID保持一致")
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("模板名称")
private String name;
@Column
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("展示模板名称")
private String templateName;
@Column
@ColDefine(type = ColType.VARCHAR, width = 255)
@Comment("模板图标")
private String templateIcon;
@Column
@ColDefine(type = ColType.VARCHAR, width = 255)
@Comment("模板封面")
private String cover;
@Column
@ColDefine(type = ColType.TEXT)
@Comment("模板内容")
private String content;
@Column
@ColDefine(type = ColType.VARCHAR, width = 1000)
@Comment("模板链接")
private String url;
@Column
@ColDefine(type = ColType.VARCHAR, width = 1000)
@Comment("h5模板链接")
private String h5Url;
@Column
@ColDefine(type = ColType.INT)
@Comment("面向人员分组ID")
private Integer allowUserGroupId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 1000)
@Comment("允许查看的人员sql")
private String allowUserSql;
@Column
@ColDefine(type = ColType.BOOLEAN)
@Comment("是否启用")
@Default("0")
private Boolean enable;
@Column
@Comment("类路径")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String classPath;
@Column
@Comment("是否置顶")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean top;
@Column
@Comment("是否推送大图")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean push;
@Column
@Comment("排序号")
@Default("0")
private Integer sortNo;
@Column
@Comment("开始时间")
@ColDefine(type = ColType.DATETIME)
private Date startDate;
@Column
@Comment("结束时间")
@ColDefine(type = ColType.DATETIME)
private Date endDate;
}
@@ -0,0 +1,17 @@
package com.budwk.app.sys.param;
import com.budwk.app.base.param.PageForm;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel("系统首页工作模板管理查询参数")
public class SysHomeTemplatePageForm extends PageForm {
@ApiModelProperty("模板名称")
private String name;
}
@@ -0,0 +1,7 @@
package com.budwk.app.sys.services;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.sys.models.Sys_home_template;
public interface SysHomeTemplateService extends BaseService<Sys_home_template> {
}
@@ -42,6 +42,8 @@ import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -50,6 +52,8 @@ import java.util.regex.Pattern;
public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements SysFileService {
static String IMG_BASE64_PATTERN = "<img\\s+[^>]*src\\s*=\\s*['\"](data:image/[^'\"]+;base64,[^'\"]+)['\"][^>]*>";
private static final int DOWNLOAD_FILE_CACHE_LIMIT = 2000;
private final Map<String, Sys_file> downloadFileCache = new ConcurrentHashMap<>();
public SysFileServiceImpl(Dao dao) {
super(dao);
@@ -77,7 +81,13 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
@Override
public void download(String id, HttpServletRequest request, HttpServletResponse response) throws IOException {
Sys_file sys_file = this.fetch(id);
if (handleCachedFileRequest(id, request, response)) {
return;
}
Sys_file sys_file = fetchDownloadFile(id);
if (!ObjectUtil.isEmpty(sys_file) && handleCachedImageRequest(sys_file, request, response)) {
return;
}
if (ObjectUtil.isEmpty(sys_file)) {
sendErrorResponse(response, "文件记录不存在");
return;
@@ -88,16 +98,108 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
sendErrorResponse(response, "找不到存储的文件");
return;
}
CommonDownloadUtil.download(sys_file.getName(), IoUtil.readBytes(FileUtil.getInputStream(file)), response);
byte[] bytes = IoUtil.readBytes(FileUtil.getInputStream(file));
if (writeImageResponse(sys_file, bytes, response)) {
return;
}
CommonDownloadUtil.download(sys_file.getName(), bytes, response);
} else if (sys_file.getEngine().equals(SysFileEngineTypeEnum.MINIO.getValue())) {
byte[] bytes = SysFileMinIoUtil.getFileBytes(sys_file.getBucket(), sys_file.getStoragePath());
if (writeImageResponse(sys_file, bytes, response)) {
return;
}
CommonDownloadUtil.download(sys_file.getName(), bytes, response);
}
}
private boolean handleCachedFileRequest(String id, HttpServletRequest request, HttpServletResponse response) {
String etag = buildFileEtag(id);
if (etag.equals(request.getHeader("If-None-Match"))) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
setImageCacheHeaders(response, etag);
return true;
}
return false;
}
private Sys_file fetchDownloadFile(String id) {
if (StrUtil.isBlank(id)) {
return null;
}
Sys_file cached = downloadFileCache.get(id);
if (cached != null) {
return cached;
}
Sys_file sysFile = this.fetch(id);
if (sysFile != null) {
if (downloadFileCache.size() >= DOWNLOAD_FILE_CACHE_LIMIT) {
downloadFileCache.clear();
}
downloadFileCache.put(id, sysFile);
}
return sysFile;
}
private boolean handleCachedImageRequest(Sys_file sysFile, HttpServletRequest request, HttpServletResponse response) {
if (!isImage(sysFile)) {
return false;
}
String etag = buildFileEtag(sysFile);
if (etag.equals(request.getHeader("If-None-Match"))) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
setImageCacheHeaders(response, etag);
return true;
}
return false;
}
private boolean writeImageResponse(Sys_file sysFile, byte[] bytes, HttpServletResponse response) throws IOException {
if (!isImage(sysFile)) {
return false;
}
setImageCacheHeaders(response, buildFileEtag(sysFile));
response.setHeader("Content-Disposition", "inline;filename=" + java.net.URLEncoder.encode(sysFile.getName(), java.nio.charset.StandardCharsets.UTF_8));
response.setHeader("Content-Length", String.valueOf(bytes.length));
response.setContentType(getImageContentType(sysFile));
IoUtil.write(response.getOutputStream(), true, bytes);
return true;
}
private void setImageCacheHeaders(HttpServletResponse response, String etag) {
response.setHeader("Cache-Control", "public, max-age=604800, immutable");
response.setHeader("ETag", etag);
response.setDateHeader("Expires", System.currentTimeMillis() + 604800000L);
}
private String buildFileEtag(Sys_file sysFile) {
return buildFileEtag(sysFile.getId());
}
private String buildFileEtag(String id) {
return "\"" + id + "\"";
}
private boolean isImage(Sys_file sysFile) {
String suffix = StrUtil.blankToDefault(sysFile.getSuffix(), FileUtil.extName(sysFile.getName())).toLowerCase();
return "jpg".equals(suffix) || "jpeg".equals(suffix) || "png".equals(suffix) || "gif".equals(suffix) || "webp".equals(suffix) || "bmp".equals(suffix) || "svg".equals(suffix);
}
private String getImageContentType(Sys_file sysFile) {
String suffix = StrUtil.blankToDefault(sysFile.getSuffix(), FileUtil.extName(sysFile.getName())).toLowerCase();
return switch (suffix) {
case "jpg", "jpeg" -> "image/jpeg";
case "png" -> "image/png";
case "gif" -> "image/gif";
case "webp" -> "image/webp";
case "bmp" -> "image/bmp";
case "svg" -> "image/svg+xml";
default -> "application/octet-stream";
};
}
@Override
public byte[] download(String id) throws IOException {
Sys_file sys_file = this.fetch(id);
Sys_file sys_file = fetchDownloadFile(id);
if (ObjectUtil.isEmpty(sys_file)) {
throw new IOException("文件记录不存在");
}
@@ -147,7 +249,7 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
@Override
public Sys_file detail(String id) {
return fetch(id);
return fetchDownloadFile(id);
}
@Override
@@ -0,0 +1,15 @@
package com.budwk.app.sys.services.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.sys.services.SysHomeTemplateService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class SysHomeTemplateServiceImpl extends BaseServiceImpl<Sys_home_template> implements SysHomeTemplateService {
public SysHomeTemplateServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,99 @@
package com.budwk.app.web.commons.filter;
import org.nutz.lang.Strings;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Locale;
public class StaticResourceCacheFilter implements Filter {
private static final int ONE_YEAR_SECONDS = 31536000;
private static final int SEVEN_DAYS_SECONDS = 604800;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
applyCacheHeaders(request, response);
chain.doFilter(req, res);
}
private void applyCacheHeaders(HttpServletRequest request, HttpServletResponse response) {
String path = Strings.sNull(request.getRequestURI());
String contextPath = Strings.sNull(request.getContextPath());
if (!contextPath.isBlank() && path.startsWith(contextPath)) {
path = path.substring(contextPath.length());
}
if (!isStaticResourcePath(path)) {
return;
}
response.setHeader("Vary", "Accept-Encoding");
if (hasVersionQuery(request)) {
setPublicCache(response, ONE_YEAR_SECONDS, true);
return;
}
if (path.startsWith("/components/")) {
response.setHeader("Cache-Control", "no-cache");
return;
}
if (path.startsWith("/favicon/") || "/favicon.ico".equals(path)) {
setPublicCache(response, SEVEN_DAYS_SECONDS, false);
return;
}
if (isCacheableAsset(path)) {
setPublicCache(response, SEVEN_DAYS_SECONDS, false);
}
}
private boolean isStaticResourcePath(String path) {
return path.startsWith("/assets/") || path.startsWith("/components/") || path.startsWith("/favicon/") || "/favicon.ico".equals(path);
}
private boolean hasVersionQuery(HttpServletRequest request) {
String version = request.getParameter("v");
return version != null && !version.isBlank();
}
private boolean isCacheableAsset(String path) {
String lowerPath = path.toLowerCase(Locale.ROOT);
return lowerPath.endsWith(".js")
|| lowerPath.endsWith(".css")
|| lowerPath.endsWith(".png")
|| lowerPath.endsWith(".jpg")
|| lowerPath.endsWith(".jpeg")
|| lowerPath.endsWith(".gif")
|| lowerPath.endsWith(".webp")
|| lowerPath.endsWith(".svg")
|| lowerPath.endsWith(".ico")
|| lowerPath.endsWith(".woff")
|| lowerPath.endsWith(".woff2")
|| lowerPath.endsWith(".ttf")
|| lowerPath.endsWith(".eot")
|| lowerPath.endsWith(".otf")
|| lowerPath.endsWith(".map");
}
private void setPublicCache(HttpServletResponse response, int maxAgeSeconds, boolean immutable) {
String cacheControl = "public, max-age=" + maxAgeSeconds;
if (immutable) {
cacheControl += ", immutable";
}
response.setHeader("Cache-Control", cacheControl);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
@@ -0,0 +1,62 @@
package com.budwk.app.web.commons.filter;
import org.nutz.boot.AppContext;
import org.nutz.boot.starter.WebFilterFace;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
@IocBean
public class StaticResourceCacheFilterStarter implements WebFilterFace {
@Inject("refer:$ioc")
protected Ioc ioc;
@Inject
protected PropertiesProxy conf;
@Inject
protected AppContext appContext;
@Override
public String getName() {
return "staticResourceCacheFilterStarter";
}
@Override
public String getPathSpec() {
return "/*";
}
@Override
public EnumSet<DispatcherType> getDispatches() {
return EnumSet.of(DispatcherType.REQUEST);
}
@IocBean(name = "staticResourceCacheFilter")
public StaticResourceCacheFilter createStaticResourceCacheFilter() {
return new StaticResourceCacheFilter();
}
@Override
public Filter getFilter() {
return ioc.get(StaticResourceCacheFilter.class, "staticResourceCacheFilter");
}
@Override
public Map<String, String> getInitParameters() {
return new HashMap<>();
}
@Override
public int getOrder() {
return 10;
}
}
@@ -149,7 +149,7 @@ public class CommonController {
@ApiOperation("系统配置")
public Result getConfigKey(String key) {
Sys_config configKey = sysDictService.dao().fetch(Sys_config.class, Cnd.where("configKey", "=", key));
return Result.success().addData(configKey.getConfigValue());
return Result.success().addData(configKey == null ? "" : configKey.getConfigValue());
}
@At
@@ -5,9 +5,12 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.ObjectUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.zhgh.activity.culture.models.ActivityTissue;
import com.budwk.app.zhgh.activity.culture.models.ActivityTissuePerson;
import com.budwk.app.zhgh.activity.culture.service.ActivityCultureService;
@@ -86,11 +89,56 @@ public class ActivityCultureInfoManageController {
return Result.success();
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("activity.culture.infoManage.school")
public Result setTemplate(@Valid String id) {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
ActivityTissue tissue = activityCultureService.fetch(id);
if (tissue == null) {
return Result.error("活动不存在");
}
if (tissue.getActivity_type() == null || tissue.getActivity_type() != 40001) {
return Result.error("仅校工会文化活动可设为模板");
}
Sys_home_template oldHomeTemplate = activityCultureService.dao().fetch(Sys_home_template.class, id);
Sys_home_template sysHomeTemplate = tissue.covertToSysHomeTemplate();
if (oldHomeTemplate != null) {
sysHomeTemplate.setTop(oldHomeTemplate.getTop());
sysHomeTemplate.setPush(oldHomeTemplate.getPush());
sysHomeTemplate.setSortNo(oldHomeTemplate.getSortNo());
if (oldHomeTemplate.getTemplateName() != null) {
sysHomeTemplate.setTemplateName(oldHomeTemplate.getTemplateName());
}
if (oldHomeTemplate.getTemplateIcon() != null) {
sysHomeTemplate.setTemplateIcon(oldHomeTemplate.getTemplateIcon());
}
}
activityCultureService.dao().insertOrUpdate(sysHomeTemplate);
return Result.success();
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("activity.culture.infoManage.school")
public Result cancelTemplate(@Valid String id) {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
activityCultureService.dao().delete(Sys_home_template.class, id);
return Result.success();
}
@At
@Aop(TransAop.READ_COMMITTED)
@SLog(tag = "文化活动", msg = "删除了一条活动")
@SaCheckPermission(value = {"activity.culture.infoManage.school", "activity.culture.infoManage.union", "activity.culture.infoManage.club"}, mode = SaMode.OR)
public Result doDelete(@Valid String id) {
if (activityCultureService.dao().fetch(Sys_home_template.class, id) != null) {
return Result.error("该活动已设为模板,请先取消模板后再删除");
}
activityCultureService.delete(id);
activityCultureService.dao().clear(ActivityTissuePerson.class, Cnd.where(ActivityTissuePerson::getTissueId, "=", id));
activityCultureService.dao().delete(Sys_home_activity.class, id);
@@ -5,6 +5,7 @@ import cn.hutool.json.JSONObject;
import com.budwk.app.base.model.BaseModel;
import com.budwk.app.base.model.CustomFormField;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.sys.services.SysHomeConvert;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -291,4 +292,29 @@ public class ActivityTissue extends BaseModel implements Serializable, SysHomeCo
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
return sysHomeActivity;
}
public Sys_home_template covertToSysHomeTemplate() {
Sys_home_template sysHomeTemplate = new Sys_home_template();
sysHomeTemplate.setId(this.getId());
sysHomeTemplate.setName(this.getName());
sysHomeTemplate.setTemplateName(this.getName());
sysHomeTemplate.setCover(this.getCover());
sysHomeTemplate.setContent(this.getActivityContent());
if (this.getActivity_type() == 40001) {
sysHomeTemplate.setUrl("/platform/activity/culture/applyActivity/school?mode=edit&id=" + this.getId());
} else if (this.getActivity_type() == 40002) {
sysHomeTemplate.setUrl("/platform/activity/culture/applyActivity/union?mode=edit&id=" + this.getId());
} else {
sysHomeTemplate.setUrl("/platform/activity/culture/applyActivity/club?mode=edit&id=" + this.getId());
}
sysHomeTemplate.setH5Url("");
if (Lang.isNotEmpty(this.getApplyStartTime())) {
sysHomeTemplate.setStartDate(DateUtil.parseDate(this.getApplyStartTime()));
sysHomeTemplate.setEndDate(DateUtil.parseDate(this.getApplyEndTime()));
}
sysHomeTemplate.setAllowUserGroupId(this.getGroupId());
sysHomeTemplate.setEnable(this.getIsUnseal());
sysHomeTemplate.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
return sysHomeTemplate;
}
}
@@ -109,7 +109,8 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
t.taskParentId,
t.variable taskVariable,
IF((SELECT taskName FROM wf_process_task tt WHERE tt.id = t.taskParentId) = 'startTask', 1, 0) canRevoke,
(select MAX(id) from wf_process_task where processInstanceId = ins.id and taskName = 'startTask') AS startTaskId
(select MAX(id) from wf_process_task where processInstanceId = ins.id and taskName = 'startTask') AS startTaskId,
IF(sht.id IS NULL, 0, 1) AS isTemplate
FROM
activity_tissue tissue
LEFT JOIN sys_union uni ON uni.id = tissue.unionId
@@ -118,6 +119,7 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = tissue.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN sys_home_template sht ON sht.id = tissue.id
$condition
""");
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.activity.sports.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant;
@@ -9,6 +10,7 @@ import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.activity.basic.models.ActivityEvent;
import com.budwk.app.zhgh.activity.sports.models.ActivitySchool;
import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolEvent;
@@ -51,6 +53,9 @@ public class ActivitySportsInfoManageController {
@Inject
private Dao dao;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
@At
@SaCheckPermission("activity.sports.info")
public Object pageData(PageForm page,
@@ -128,7 +133,7 @@ public class ActivitySportsInfoManageController {
@At
@SaCheckPermission("activity.sports.info")
@SaCheckPermission(value = {"activity.sports.info", "activity.sports.new"}, mode = SaMode.OR)
public Object getEventApply(@Param(value = "activityId") String activityId,
@Param(value = "eventId") String[] eventId) {
Cnd cnd = Cnd.NEW();
@@ -149,7 +154,7 @@ public class ActivitySportsInfoManageController {
@At
@SLog(tag = "体育活动", msg = "修改活动")
@SaCheckPermission("activity.sports.info")
@SaCheckPermission(value = {"activity.sports.info", "activity.sports.new"}, mode = SaMode.OR)
public Result doEdit(@Param(value = "data") ActivitySchool activitySchool,
@Param(value = "events") ActivitySchoolEvent[] events) {
activitySportsService.doEdit(activitySchool, events);
@@ -160,7 +165,7 @@ public class ActivitySportsInfoManageController {
@At
@SLog(tag = "体育活动", msg = "添加活动")
@SaCheckPermission("activity.sports.info")
@SaCheckPermission(value = {"activity.sports.info", "activity.sports.new"}, mode = SaMode.OR)
public Result doAdd(@Param(value = "data") ActivitySchool activitySchool,
@Param(value = "events") ActivitySchoolEvent[] events) {
String id = activitySportsService.doAdd(activitySchool, events);
@@ -170,7 +175,7 @@ public class ActivitySportsInfoManageController {
}
@At
@SaCheckPermission("activity.sports.info")
@SaCheckPermission(value = {"activity.sports.info", "activity.sports.new"}, mode = SaMode.OR)
public Result findOne(String id) {
NutMap nutMap = (NutMap) dao.execute(Sqls.create("SELECT * FROM `activity_school` WHERE id = @id").setParam("id", id)
.setCallback(Sqls.callback.map())).getResult();
@@ -199,7 +204,7 @@ public class ActivitySportsInfoManageController {
@At
@ApiOperation("根据比赛项目基础表统一赋年龄限制")
@SaCheckPermission("activity.sports.info")
@SaCheckPermission(value = {"activity.sports.info", "activity.sports.new"}, mode = SaMode.OR)
public Object doAssignmentDate(ActivitySchoolEvent[] events) {
for (ActivitySchoolEvent event : events) {
ActivityEvent activityEvent = dao.fetch(ActivityEvent.class,event.getEventId());
@@ -212,6 +217,28 @@ public class ActivitySportsInfoManageController {
return Result.success(events);
}
@At
@ApiOperation("获取分工会人数限制")
@SaCheckPermission(value = {"activity.sports.info", "activity.sports.new"}, mode = SaMode.OR)
public Result getUnionLimit(@Param(value = "activityScopeId") String activityScopeId) {
Sql sql = Sqls.create("""
SELECT
gh.id,
gh.name,
gh.unioncode,
(select count(1) from `vw_user` where unionid = gh.id $cnd) as teacherCount,
NULL as ratio,
NULL as limitCount
FROM
sys_union gh
order by gh.unioncode
""");
if (StrUtil.isNotBlank(activityScopeId)) {
sql.setVar("cnd", "AND id in (" + activityBasicScopeService.buildGroupUserIdSubSqlText(Integer.valueOf(activityScopeId)) + ")");
}
return Result.success(activitySportsService.listMap(sql));
}
@At
@Ok("void")
@@ -0,0 +1,21 @@
package com.budwk.app.zhgh.activity.sports.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import io.swagger.annotations.Api;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
@IocBean
@At("/platform/activity/sports/new")
@Ok("json:full")
@Api(tags = "新建体育活动")
public class ActivitySportsNewController {
@At("")
@Ok("beetl:platform/zhgh/activity/sports/new/index.html")
@SaCheckPermission(value = {"activity.sports.info", "activity.sports.new"}, mode = SaMode.OR)
public void index() {
}
}
@@ -0,0 +1,20 @@
package com.budwk.app.zhgh.activity.trainSignUp.controller.manage;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.annotations.Api;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
@IocBean
@At("/platform/trainSignUp/applyActivity")
@Ok("json:full")
@Api(tags = "品牌活动新建活动")
public class TrainSignUpApplyActivityController {
@At("")
@Ok("beetl:/platform/zhgh/activity/trainSignUp/applyActivity/index.html")
@SaCheckPermission("trainSignUp.applyActivity")
public void index() {
}
}
@@ -1,16 +1,21 @@
package com.budwk.app.zhgh.activity.trainSignUp.controller.manage;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant;
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.flow.entity.ProcessInstance;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.sys.models.Sys_unit;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.activity.trainSignUp.models.*;
import com.budwk.app.zhgh.activity.trainSignUp.service.TrainSignUpActivityService;
@@ -72,7 +77,54 @@ public class TrainSignUpManageController {
cnd.andEX("year", "=", year);
cnd.and(Cnd.likeEX("activityName", activityName));
cnd.orderBy("createdAt", "desc");
return Result.success().addData(trainSignUpActivityManageService.pageData(pageForm, cnd));
Pagination<TrainSignUpActivity> pagination = trainSignUpActivityManageService.pageData(pageForm, cnd);
List<TrainSignUpActivity> list = pagination.getList(TrainSignUpActivity.class);
if (list != null && !list.isEmpty()) {
List<String> ids = list.stream().map(TrainSignUpActivity::getId).toList();
List<Sys_home_template> templateList = dao.query(Sys_home_template.class, Cnd.where("id", "in", ids));
List<String> templateIds = templateList.stream().map(Sys_home_template::getId).toList();
list.forEach(activity -> activity.setIsTemplate(templateIds.contains(activity.getId())));
}
return Result.success().addData(pagination);
}
@At
@ApiOperation("设为模板")
@SaCheckPermission("trainSignUp.manage")
public Result setTemplate(String id) {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
TrainSignUpActivity activity = trainSignUpActivityManageService.fetch(id);
if (activity == null) {
return Result.error("活动不存在");
}
Sys_home_template oldHomeTemplate = dao.fetch(Sys_home_template.class, id);
Sys_home_template sysHomeTemplate = activity.covertToSysHomeTemplate();
if (oldHomeTemplate != null) {
sysHomeTemplate.setTop(oldHomeTemplate.getTop());
sysHomeTemplate.setPush(oldHomeTemplate.getPush());
sysHomeTemplate.setSortNo(oldHomeTemplate.getSortNo());
if (oldHomeTemplate.getTemplateName() != null) {
sysHomeTemplate.setTemplateName(oldHomeTemplate.getTemplateName());
}
if (oldHomeTemplate.getTemplateIcon() != null) {
sysHomeTemplate.setTemplateIcon(oldHomeTemplate.getTemplateIcon());
}
}
dao.insertOrUpdate(sysHomeTemplate);
return Result.success();
}
@At
@ApiOperation("取消模板")
@SaCheckPermission("trainSignUp.manage")
public Result cancelTemplate(String id) {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
return Result.error("无权操作");
}
dao.delete(Sys_home_template.class, id);
return Result.success();
}
@At
@@ -80,6 +132,9 @@ public class TrainSignUpManageController {
@SaCheckPermission("trainSignUp.manage")
@SLog(tag = "品牌活动-活动管理", msg = "删除活动")
public Result onDelete(String id) {
if (dao.fetch(Sys_home_template.class, id) != null) {
return Result.error("该活动已设为模板,请先取消模板后再删除");
}
Trans.exec(() -> {
trainSignUpActivityManageService.delete(id);
dao.clear(TrainSignUpCourse.class, Cnd.where("activityId", "=", id));
@@ -106,7 +161,7 @@ public class TrainSignUpManageController {
@At
@ApiOperation("查询单个活动")
@SaCheckPermission("trainSignUp")
@SaCheckPermission(value = {"trainSignUp", "trainSignUp.manage", "trainSignUp.applyActivity", "h5.trainSignUp.apply"}, mode = SaMode.OR)
public Result findOne(@Param("id") @NotNull String id) {
NutMap dataMap = trainSignUpActivityManageService.findOne(id, null, "");
String activityStartTime = dataMap.getString("activityStartTime");
@@ -154,7 +209,7 @@ public class TrainSignUpManageController {
@At
@Ok("json:full")
@ApiOperation("品牌活动新增/修改")
@SaCheckPermission("trainSignUp.manage")
@SaCheckPermission(value = {"trainSignUp.manage", "trainSignUp.applyActivity"}, mode = SaMode.OR)
@SLog(tag = "品牌活动-活动管理", msg = "新增/修改活动")
public Result doHandle(TrainSignUpActivity activity) {
if (StrUtil.isBlank(activity.getId())) {
@@ -168,7 +223,7 @@ public class TrainSignUpManageController {
@At
@Ok("json:full")
@ApiOperation("获取分工会人数限制")
@SaCheckPermission("trainSignUp.manage")
@SaCheckPermission(value = {"trainSignUp.manage", "trainSignUp.applyActivity"}, mode = SaMode.OR)
public Result getUnionLimit(@Param(value = "activityScopeId") String activityScopeId) {
Sql sql = Sqls.create("""
SELECT
@@ -192,7 +247,7 @@ public class TrainSignUpManageController {
@At
@Ok("json:full")
@ApiOperation("获取报名人员数量")
@SaCheckPermission("trainSignUp.manage")
@SaCheckPermission(value = {"trainSignUp.manage", "trainSignUp.applyActivity"}, mode = SaMode.OR)
public Result getRegisterUserCount(@Param(value = "courseId") String courseId) {
return Result.success().addData(dao.count(TrainSignUpUser.class, Cnd.where("courseId", "=", courseId)));
}
@@ -200,7 +255,7 @@ public class TrainSignUpManageController {
@At
@Ok("json:full")
@ApiOperation("获取历史活动列表")
@SaCheckPermission("trainSignUp.manage")
@SaCheckPermission(value = {"trainSignUp.manage", "trainSignUp.applyActivity"}, mode = SaMode.OR)
public Result getHistoricalActList() {
List<TrainSignUpActivity> query = dao.query(TrainSignUpActivity.class, Cnd.NEW().desc("activityStartTime"));
return Result.success().addData(query);
@@ -209,7 +264,7 @@ public class TrainSignUpManageController {
@At
@Ok("json:full")
@ApiOperation("查询分工会和社团")
@SaCheckPermission("trainSignUp.manage")
@SaCheckPermission(value = {"trainSignUp.manage", "trainSignUp.applyActivity"}, mode = SaMode.OR)
public Result selectUnitAndClub() {
List<NutMap> result = new ArrayList<>();
List<Sys_unit> unitList = dao.query(Sys_unit.class, Cnd.NEW().asc(Sys_unit::getUnitcode));
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.activity.trainSignUp.models;
import com.budwk.app.base.model.BaseModel;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.sys.services.SysHomeConvert;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -109,6 +110,8 @@ public class TrainSignUpActivity extends BaseModel implements Serializable, SysH
@Many(field = "activityId")
private List<TrainSignUpTypeLimit> typeLimits;
private Boolean isTemplate;
@Column
@Comment("活动类型")
@ColDefine(type = ColType.VARCHAR, width = 20)
@@ -141,4 +144,23 @@ public class TrainSignUpActivity extends BaseModel implements Serializable, SysH
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
return sysHomeActivity;
}
public Sys_home_template covertToSysHomeTemplate() {
Sys_home_template sysHomeTemplate = new Sys_home_template();
sysHomeTemplate.setId(this.getId());
sysHomeTemplate.setName(this.getActivityName());
sysHomeTemplate.setTemplateName(this.getActivityName());
sysHomeTemplate.setCover(this.getCover());
sysHomeTemplate.setContent(this.getIntroduce());
sysHomeTemplate.setUrl("/platform/trainSignUp/applyActivity?mode=edit&id=" + this.getId());
sysHomeTemplate.setH5Url("");
if (Lang.isNotEmpty(this.getActivitySignUpStartTime())) {
sysHomeTemplate.setStartDate(this.getActivitySignUpStartTime());
sysHomeTemplate.setEndDate(this.getActivitySignUpEndTime());
}
sysHomeTemplate.setAllowUserGroupId(this.getActivityGroupId());
sysHomeTemplate.setEnable(!this.isDisabled());
sysHomeTemplate.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
return sysHomeTemplate;
}
}
@@ -105,12 +105,12 @@ public class AssetManageController {
@SLog(type = "assetManage", tag = "资产管理", msg = "编辑了一条资产信息")
@SaCheckPermission("asset.manage")
public Result doEdit(Asset asset) {
Asset fetch = assetService.fetch(asset.getId());
assetService.update(asset);
//增加折旧表记录
Asset fetch = assetService.fetch(asset.getId());
//如果资产类型不等于原来的或者预计报废时间不等于原来的
if (!fetch.getAssetCategoryId().equals(asset.getAssetCategoryId()) || !fetch.getAssetRetiredAssetsDate().equals(asset.getAssetRetiredAssetsDate())) {
if (!ObjectUtil.equal(fetch.getAssetCategoryId(), asset.getAssetCategoryId()) || !ObjectUtil.equal(fetch.getAssetRetiredAssetsDate(), asset.getAssetRetiredAssetsDate())) {
assetDepreciationRecordService.clear(Cnd.where("assetId", "=", asset.getId()));
AssetDepreciationRecord record = assetDepreciationRecordService.initAssetDepreciationRecord(asset.getAssetUnitPrice(), asset.getAssetQuantity(), asset.getAssetUsedDate(), asset.getAssetRetiredAssetsDate());
if (ObjectUtil.isNotEmpty(record)) {
@@ -70,7 +70,7 @@ public class Asset extends BaseModel implements Serializable {
@Column
@Comment("单价")
@ColDefine(customType = "decimal(10,2)")
@ColDefine(customType = "decimal(18,2)")
private BigDecimal assetUnitPrice;
@Column
@@ -43,22 +43,22 @@ public class AssetDepreciationRecord extends BaseModel implements Serializable {
@Column
@Comment("资产全部的价值")
@ColDefine(customType = "decimal(10,2)")
@ColDefine(customType = "decimal(18,2)")
private BigDecimal assetAllMoney;
@Column
@Comment("剩余价值")
@ColDefine(customType = "decimal(10,2)")
@ColDefine(customType = "decimal(18,2)")
private BigDecimal assetSurplusMoney;
@Column
@Comment("累计折旧多少钱")
@ColDefine(customType = "decimal(10,2)")
@ColDefine(customType = "decimal(18,2)")
private BigDecimal assetDepreciationMoney;
@Column
@Comment("平均一个月多少钱")
@ColDefine(customType = "decimal(10,2)")
@ColDefine(customType = "decimal(18,2)")
private BigDecimal assetAverageMonthMoney;
@Column
@@ -53,6 +53,16 @@ public class AssetDepreciationRecordServiceImpl extends BaseServiceImpl<AssetDep
//计算资产使用时间到现在使用了几个月
long usedMonth = ChronoUnit.MONTHS.between(assetUsedDateLocalDate, LocalDate.now());
AssetDepreciationRecord record = new AssetDepreciationRecord();
BigDecimal assetAllMoney = assetUnitPrice.multiply(new BigDecimal(assetQuantity));
if (wholeMonth <= 0) {
record.setAssetRetiredAssetsDate(assetRetiredAssetsDate);
record.setAssetRemainingMonths(0);
record.setAssetAllMoney(assetAllMoney);
record.setAssetAverageMonthMoney(assetAllMoney);
record.setAssetSurplusMoney(BigDecimal.ZERO);
record.setAssetDepreciationMoney(assetAllMoney);
return record;
}
//如果折旧到期时间小于现在时间直接设为0
if (assetRetiredAssetsDateLocalDate.isBefore(LocalDate.now())) {
//预计折旧到期时间
@@ -60,12 +70,12 @@ public class AssetDepreciationRecordServiceImpl extends BaseServiceImpl<AssetDep
//资产剩余的月
record.setAssetRemainingMonths(0);
//资产全部的金额
record.setAssetAllMoney(assetUnitPrice.multiply(new BigDecimal(assetQuantity)));
record.setAssetAllMoney(assetAllMoney);
//资产平均每月价值
record.setAssetAverageMonthMoney(record.getAssetAllMoney().divide(new BigDecimal(wholeMonth), 2, RoundingMode.DOWN));
//剩余价值
record.setAssetSurplusMoney(BigDecimal.ZERO);
record.setAssetDepreciationMoney(assetUnitPrice.multiply(new BigDecimal(assetQuantity)));
record.setAssetDepreciationMoney(assetAllMoney);
return record;
} else if (wholeMonth - usedMonth >= 0) {
//预计折旧到期时间
@@ -73,13 +83,13 @@ public class AssetDepreciationRecordServiceImpl extends BaseServiceImpl<AssetDep
//资产剩余的月
record.setAssetRemainingMonths((int) (wholeMonth - usedMonth < 0 ? 0 : wholeMonth - usedMonth));
//资产全部的金额
record.setAssetAllMoney(assetUnitPrice.multiply(new BigDecimal(assetQuantity)));
record.setAssetAllMoney(assetAllMoney);
//资产平均每月价值
record.setAssetAverageMonthMoney(record.getAssetAllMoney().divide(new BigDecimal(wholeMonth), 2, RoundingMode.DOWN));
//剩余价值
if (usedMonth == 0) {
//如果等于0代表一个月都没用到直接设为全部的金额
record.setAssetSurplusMoney(assetUnitPrice.multiply(new BigDecimal(assetQuantity)));
record.setAssetSurplusMoney(assetAllMoney);
} else {
record.setAssetSurplusMoney(record.getAssetAverageMonthMoney().multiply(BigDecimal.valueOf(record.getAssetRemainingMonths())));
}
@@ -1,18 +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.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;
@@ -22,10 +21,12 @@ 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.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.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
@@ -34,7 +35,6 @@ 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;
@@ -67,9 +67,14 @@ public class BuildHomeLittleHouseController {
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 (!isSysAdmin()) {
cnd.and("unionId", "=", SecurityUtil.getUnionId());
}
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
cnd.where().andLike("unitName", 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);
@@ -77,12 +82,19 @@ public class BuildHomeLittleHouseController {
return Result.success(pagination);
}
@At
@SaCheckPermission("buildHome.littleHouse")
public Result currentUserOrg() {
return Result.success(getCurrentUserOrg());
}
@At
@SaCheckPermission("buildHome.littleHouse")
public Result save(BuildHomeLittleHouse house) {
if (house.getId() == null) {
house.setUnionId(SecurityUtil.getUnionId());
house.setUnionName(SecurityUtil.getUnionId());
fillCurrentUserOrg(house);
} else {
keepOriginalOrg(house);
}
dao.insertOrUpdate(house);
return Result.success();
@@ -102,22 +114,64 @@ public class BuildHomeLittleHouseController {
return Result.success(house);
}
@At
@SaCheckPermission("buildHome.littleHouse")
public Result saveCoordinate(Long id, Double mapX, Double mapY) {
if (!isSysAdmin()) {
return Result.error("仅系统管理员可以采集坐标");
}
if (id == null) {
return Result.error("小家记录ID不能为空");
}
if (!isValidCoordinate(mapX) || !isValidCoordinate(mapY)) {
return Result.error("坐标范围必须在0到100之间");
}
BuildHomeLittleHouse house = dao.fetch(BuildHomeLittleHouse.class, id);
if (house == null) {
return Result.error("未找到小家记录");
}
dao.update(BuildHomeLittleHouse.class, Chain.make("mapX", mapX).add("mapY", mapY), Cnd.where("id", "=", id));
return Result.success();
}
@At
@SaCheckPermission("buildHome.littleHouse")
public Result clearCoordinate(Long id) {
if (!isSysAdmin()) {
return Result.error("仅系统管理员可以清空坐标");
}
if (id == null) {
return Result.error("小家记录ID不能为空");
}
BuildHomeLittleHouse house = dao.fetch(BuildHomeLittleHouse.class, id);
if (house == null) {
return Result.error("未找到小家记录");
}
dao.update(BuildHomeLittleHouse.class, Chain.make("mapX", null).add("mapY", null), Cnd.where("id", "=", id));
return Result.success();
}
@At("/exportXlsx")
@SaCheckPermission("buildHome.littleHouse")
@Ok("void")
public void exportXlsx(HttpServletResponse response) {
try {
boolean sysAdmin = isSysAdmin();
String unionId = SecurityUtil.getUnionId();
Cnd cnd = Cnd.where("unionId", "=", unionId);
Cnd cnd = Cnd.NEW();
if (!sysAdmin) {
cnd.and("unionId", "=", unionId);
}
cnd.asc("sortNum");
Sys_union union = dao.fetch(Sys_union.class, unionId);
if (union == null) {
if (!sysAdmin && union == null) {
throw new BaseException("未找到ID为 " + unionId + " 的工会信息");
}
// 准备文档数据Map
Map<String, Object> docMap = new HashMap<>();
docMap.put("unionName", union.getName()); // 工会名称
docMap.put("unionName", sysAdmin ? "全部工会" : union.getName()); // 工会名称
docMap.put("date", DateUtil.today()); // 当前日期
// 查询小家建设数据并设置序号
@@ -151,4 +205,50 @@ public class BuildHomeLittleHouseController {
}
}
private void fillCurrentUserOrg(BuildHomeLittleHouse house) {
Map<String, String> currentUserOrg = getCurrentUserOrg();
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 Map<String, String> getCurrentUserOrg() {
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;
}
private boolean isSysAdmin() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name());
}
private boolean isValidCoordinate(Double value) {
return value != null && value >= 0 && value <= 100;
}
}
@@ -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,570 @@
package com.budwk.app.zhgh.dayofficework.caredata;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import io.swagger.annotations.Api;
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.json.Json;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
@IocBean
@At("/platform/careData/leader")
@Api("Leader dashboard")
@Ok("json:full")
public class CareDataLeaderCon {
private static final int FINISHED_STATE = 20;
@Inject
private Dao dao;
@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) {
String currentSessionId = StrUtil.blankToDefault(sessionId, latestSessionId());
if (StrUtil.isBlank(currentSessionId)) {
return Result.success(NutMap.NEW());
}
NutMap session = sessionInfo(currentSessionId);
List<NutMap> caseResults = caseResultData(currentSessionId);
NutMap overview = overviewData(currentSessionId, caseResults);
return Result.success(NutMap.NEW()
.addv("session", session)
.addv("overview", overview)
.addv("caseResults", caseResults));
}
@At
@SaCheckPermission("careData.union")
public Result 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 Result.success(NutMap.NEW()
.addv("year", targetYear)
.addv("total", total)
.addv("types", topRows));
}
@At
@SaCheckPermission("careData.union")
public Result baseUnionData() {
return Result.success(NutMap.NEW().addv("unions", baseUnionRows()));
}
@At
@SaCheckPermission("careData.union")
public Result clubData() {
return Result.success(NutMap.NEW().addv("clubs", clubRows()));
}
@At
@SaCheckPermission("careData.union")
public Result dataMetricData(Integer year) {
int targetYear = year == null ? LocalDate.now().getYear() : year;
return Result.success(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)));
}
@At
@SaCheckPermission("careData.union")
public Result assetDataData() {
List<NutMap> states = assetUsageStateRows();
long total = states.stream().mapToLong(row -> row.getLong("value", 0L)).sum();
return Result.success(NutMap.NEW()
.addv("total", total)
.addv("states", states));
}
@At
@SaCheckPermission("careData.union")
public Result staffHomeData() {
return Result.success(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
""");
if (canViewAllUnionData()) {
sql.setVar("unionFilter", "");
} else {
sql.setVar("unionFilter", "AND unionId = @unionId");
sql.setParam("unionId", SecurityUtil.getUnionId());
}
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);
if (canViewAllUnionData()) {
sql.setVar("unionFilter", "");
} else {
sql.setVar("unionFilter", "AND info.applyUnionId = @unionId");
sql.setParam("unionId", SecurityUtil.getUnionId());
}
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
""");
if (canViewAllUnionData()) {
sql.setVar("unionFilter", "");
} else {
sql.setVar("unionFilter", "AND un.id = @unionId");
sql.setParam("unionId", SecurityUtil.getUnionId());
}
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", "\u672a\u586b\u5199");
if (canViewAllUnionData()) {
sql.setVar("unionFilter", "");
} else {
sql.setVar("unionFilter", "AND t1.assetUseUnionId = @unionId");
sql.setParam("unionId", SecurityUtil.getUnionId());
}
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 firstMap(sql).getAs("total", BigDecimal.class);
}
private long tourCount(int year) {
Sql sql = Sqls.create("""
SELECT COUNT(DISTINCT t.id) AS count
FROM tour_ledger t
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
WHERE t.delFlag = 0
AND t.`year` = @year
AND (ins.id IS NULL OR ins.state = @finished)
$unionFilter
""");
sql.setParam("year", year);
sql.setParam("finished", FINISHED_STATE);
if (canViewAllUnionData()) {
sql.setVar("unionFilter", "");
} else {
sql.setVar("unionFilter", "AND t.unionId = @unionId");
sql.setParam("unionId", SecurityUtil.getUnionId());
}
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);
if (canViewAllUnionData()) {
sql.setVar("unionFilter", "");
} else {
sql.setVar("unionFilter", "AND h.unionId = @unionId");
sql.setParam("unionId", SecurityUtil.getUnionId());
}
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);
if (canViewAllUnionData()) {
sql.setVar("unionFilter", "");
} else {
sql.setVar("unionFilter", "AND info.unionId = @unionId");
sql.setParam("unionId", SecurityUtil.getUnionId());
}
return firstMap(sql).getLong("count", 0L);
}
private BigDecimal reimburseTotal(int year) {
Sql sql = Sqls.create("""
SELECT
IFNULL(SUM(
CASE
WHEN info.realMoney IS NOT NULL THEN info.realMoney
WHEN info.money IS NOT NULL THEN info.money
WHEN info.condolenceMoney IS NOT NULL THEN info.condolenceMoney
ELSE 0
END
), 0) AS total
FROM union_reimburse info
WHERE info.delFlag = 0
AND info.stateId IN (2, 3)
AND YEAR(info.createTime) = @year
$unionFilter
""");
sql.setParam("year", year);
if (canViewAllUnionData()) {
sql.setVar("unionFilter", "");
} else {
sql.setVar("unionFilter", "AND info.unionId = @unionId");
sql.setParam("unionId", SecurityUtil.getUnionId());
}
return firstMap(sql).getAs("total", BigDecimal.class);
}
private boolean canViewAllUnionData() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
}
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, "\u4e0d\u4e88", "\u4e0d\u7acb\u6848")) {
rejectedCount += count;
} else if (containsAny(name, "\u610f\u89c1", "\u5efa\u8bae")) {
suggestionCount += count;
} else if (name.contains("\u7acb\u6848")) {
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("\u6ee1\u610f") && !name.contains("\u4e0d\u6ee1\u610f");
})
.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 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();
}
}
@@ -55,6 +55,43 @@ public class HonorManageController {
}
@At("/h5")
@SaCheckLogin
@Ok("beetl:/platform/zhghh5/dayofficework/honor/manage/index.html")
public void h5() {
}
@At
@SaCheckLogin
public Result h5Data(HonorPageForm pageForm) {
Sql sql = Sqls.create("""
SELECT
h.id,
h.userName,
h.applyUnionName,
h.unionName,
h.grantDate,
h.photoFiles,
prize.`name` AS prizeName,
type.`name` AS typeName,
type.queryTypeCode AS typeQueryTypeCode
FROM
`honor` h
LEFT JOIN honor_basic_settings prize ON prize.id = h.prize
LEFT JOIN honor_basic_settings type ON type.id = h.honorType
$condition
""");
Cnd cnd = Cnd.NEW();
Integer year = pageForm.getYear() == null ? Calendar.getInstance().get(Calendar.YEAR) : pageForm.getYear();
cnd.andEX("YEAR(h.grantDate)", "=", year);
cnd.andEX("h.honorType", "=", pageForm.getHonorType());
cnd.desc("h.grantDate");
sql.setCondition(cnd);
Pagination pagination = honorViService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
}
@At
@SaCheckPermission("honor.manage")
public Result pageData(HonorPageForm pageForm) {
@@ -222,5 +222,10 @@ public class Honor extends BaseModel {
@Comment("附件")
private List<JSONObject> files;
@Column
@ColDefine(type = ColType.MYSQL_JSON)
@Comment("荣誉照片")
private List<JSONObject> photoFiles;
private String loginname;
}
@@ -92,8 +92,22 @@ public class CondolenceMineController {
condolence info
LEFT JOIN condolence_type type ON info.type = type.id
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 = 10
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN (
SELECT
id,
processInstanceId,
taskName,
displayName,
taskType,
performType,
taskState,
finishTime,
taskParentId,
variable,
ROW_NUMBER() OVER (PARTITION BY processInstanceId ORDER BY createdAt DESC, id DESC) AS rn
FROM wf_process_task
WHERE taskState = 10
) t ON t.processInstanceId = ins.id AND t.rn = 1
$condition
""");
Cnd cnd = Cnd.NEW();
@@ -0,0 +1,8 @@
ALTER TABLE `asset`
MODIFY COLUMN `assetUnitPrice` DECIMAL(18, 2) NULL COMMENT '单价';
ALTER TABLE `asset_depreciation_record`
MODIFY COLUMN `assetAllMoney` DECIMAL(18, 2) NULL COMMENT '资产全部的价值',
MODIFY COLUMN `assetSurplusMoney` DECIMAL(18, 2) NULL COMMENT '剩余价值',
MODIFY COLUMN `assetDepreciationMoney` DECIMAL(18, 2) NULL COMMENT '累计折旧多少钱',
MODIFY COLUMN `assetAverageMonthMoney` DECIMAL(18, 2) NULL COMMENT '平均一个月多少钱';
@@ -0,0 +1,3 @@
ALTER TABLE `build_home_little_house`
ADD COLUMN `mapX` DECIMAL(8,4) NULL COMMENT '地图X坐标百分比' AFTER `mediaFiles`,
ADD COLUMN `mapY` DECIMAL(8,4) NULL COMMENT '地图Y坐标百分比' AFTER `mapX`;
@@ -0,0 +1,3 @@
ALTER TABLE `build_home_little_house`
ADD COLUMN `unitId` VARCHAR(32) NULL COMMENT '单位ID' AFTER `unitName`,
ADD COLUMN `mediaFiles` JSON NULL COMMENT '图片视频资料' AFTER `openTime`;
@@ -0,0 +1,2 @@
ALTER TABLE `honor`
ADD COLUMN `photoFiles` JSON NULL COMMENT '荣誉照片' AFTER `files`;
@@ -0,0 +1,64 @@
-- Home work template menu. Prefer a sibling of sys.homeActivity, then sys.manager, then sys.
INSERT INTO sys_menu (
id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled,
permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt,
delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService
)
SELECT
'e6ed122326a8492fa7f415f97eaf4f01',
parent.id,
CONCAT(parent.path, LPAD(IFNULL(child.maxNo, 0) + 1, 4, '0')),
'Work Template',
'Work Template',
'menu',
'/platform/sys/worktemplate',
'data-pjax',
'',
1,
0,
'sys.worktemplate',
NULL,
IFNULL(child.maxLocation, 0) + 1,
0,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
0,
'PC',
NULL,
NULL,
'g',
0,
0
FROM (
SELECT p.*
FROM sys_menu p
WHERE p.id = (SELECT h.parentId FROM sys_menu h WHERE h.permission = 'sys.homeActivity' LIMIT 1)
OR p.permission IN ('sys.manager', 'sys')
ORDER BY
CASE
WHEN p.id = (SELECT h.parentId FROM sys_menu h WHERE h.permission = 'sys.homeActivity' LIMIT 1) THEN 0
WHEN p.permission = 'sys.manager' THEN 1
ELSE 2
END
LIMIT 1
) parent
LEFT JOIN (
SELECT parentId,
MAX(CAST(RIGHT(path, 4) AS UNSIGNED)) AS maxNo,
MAX(location) AS maxLocation
FROM sys_menu
GROUP BY parentId
) child ON child.parentId = parent.id
WHERE NOT EXISTS (
SELECT 1 FROM sys_menu WHERE permission = 'sys.worktemplate'
);
INSERT INTO sys_role_menu (roleId, menuId)
SELECT r.id, m.id
FROM sys_role r
JOIN sys_menu m ON m.permission = 'sys.worktemplate'
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
WHERE r.code = 'SYSADMIN'
AND rm.roleId IS NULL;
@@ -0,0 +1,2 @@
ALTER TABLE `sys_config`
MODIFY COLUMN `configValue` TEXT;
@@ -0,0 +1,2 @@
ALTER TABLE `sys_home_template`
ADD COLUMN `templateIcon` varchar(255) DEFAULT NULL COMMENT 'template icon' AFTER `templateName`;
@@ -0,0 +1,6 @@
ALTER TABLE `sys_home_template`
ADD COLUMN `templateName` varchar(50) DEFAULT NULL COMMENT 'display template name' AFTER `name`;
UPDATE `sys_home_template`
SET `templateName` = `name`
WHERE (`templateName` IS NULL OR `templateName` = '');
@@ -0,0 +1,25 @@
CREATE TABLE IF NOT EXISTS `sys_home_template` (
`id` varchar(32) NOT NULL COMMENT 'business id',
`name` varchar(50) DEFAULT NULL COMMENT 'template name',
`templateName` varchar(50) DEFAULT NULL COMMENT 'display template name',
`templateIcon` varchar(255) DEFAULT NULL COMMENT 'template icon',
`cover` varchar(255) DEFAULT NULL COMMENT 'template cover',
`content` text COMMENT 'template content',
`url` varchar(1000) DEFAULT NULL COMMENT 'pc url',
`h5Url` varchar(1000) DEFAULT NULL COMMENT 'h5 url',
`allowUserGroupId` int DEFAULT NULL COMMENT 'allowed user group id',
`allowUserSql` varchar(1000) DEFAULT NULL COMMENT 'allowed user sql',
`enable` tinyint(1) DEFAULT 0 COMMENT 'enabled',
`classPath` varchar(500) DEFAULT NULL COMMENT 'source class path',
`top` tinyint(1) DEFAULT 0 COMMENT 'top flag',
`push` tinyint(1) DEFAULT 0 COMMENT 'push flag',
`sortNo` int DEFAULT 0 COMMENT 'sort number',
`startDate` datetime DEFAULT NULL COMMENT 'start date',
`endDate` datetime DEFAULT NULL COMMENT 'end date',
`createdBy` varchar(32) DEFAULT NULL,
`createdAt` bigint DEFAULT NULL,
`updatedBy` varchar(32) DEFAULT NULL,
`updatedAt` bigint DEFAULT NULL,
`delFlag` tinyint(1) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='home work template';
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@@ -18,6 +18,7 @@
recognition: null,
listening: false,
menus: null,
platform: "PC",
parentMap: {},
lastText: "",
pendingMatches: [],
@@ -128,7 +129,39 @@
}
}
function getMenus() {
function getCurrentPlatform() {
const $button = $("#voice-menu-btn")
return String(($button.data("platform") || state.platform || "PC")).toUpperCase()
}
function setCurrentPlatform(platform) {
const nextPlatform = String(platform || "PC").toUpperCase()
if (state.platform !== nextPlatform) {
state.menus = null
state.parentMap = {}
}
state.platform = nextPlatform
return state.platform
}
function filterMenusByPlatform(menus, platform) {
const currentPlatform = String(platform || "PC").toUpperCase()
return (menus || []).filter(function (menu) {
return String(menu.platform || "PC").toUpperCase() === currentPlatform
})
}
function setMenus(menus, platform) {
state.parentMap = {}
state.menus = filterMenusByPlatform(flattenMenus(menus, "", []), platform).filter(function (menu) {
return menu.href
})
return state.menus
}
function getMenus(platform) {
platform = String(platform || getCurrentPlatform()).toUpperCase()
setCurrentPlatform(platform)
const cached = state.menus
if (cached && cached.length) {
return $.Deferred().resolve(cached).promise()
@@ -136,35 +169,22 @@
const storeMenus = getStoreMenus()
if (storeMenus && storeMenus.length) {
state.menus = flattenMenus(storeMenus, "", []).filter(function (menu) {
return menu.href
})
return $.Deferred().resolve(state.menus).promise()
return $.Deferred().resolve(setMenus(storeMenus, platform)).promise()
}
return $.get("/platform/sys/user/getLogonUser").then(function (res) {
if (res && res.code === 0 && res.data && res.data.menus) {
state.parentMap = {}
state.menus = flattenMenus(res.data.menus, "", []).filter(function (menu) {
return menu.href
})
return state.menus
return setMenus(res.data.menus, platform)
}
const sessionMenus = getSessionMenus()
if (sessionMenus && sessionMenus.length) {
state.menus = flattenMenus(sessionMenus, "", []).filter(function (menu) {
return menu.href
})
return state.menus
return setMenus(sessionMenus, platform)
}
return []
}, function () {
const sessionMenus = getSessionMenus()
if (sessionMenus && sessionMenus.length) {
state.menus = flattenMenus(sessionMenus, "", []).filter(function (menu) {
return menu.href
})
return state.menus
return setMenus(sessionMenus, platform)
}
return []
})
@@ -409,7 +429,7 @@
})
return
}
getMenus().then(function (menus) {
getMenus(getCurrentPlatform()).then(function (menus) {
const matches = matchMenus(state.lastText, menus)
runAfterRecognitionEnd(function () {
chooseMenu(matches)
@@ -423,6 +443,7 @@
function init() {
$(document).on("click", "#voice-menu-btn", function () {
setCurrentPlatform($(this).data("platform") || "PC")
start()
})
}
@@ -10,7 +10,7 @@
:on-exceed="handleExceed"
:on-remove="handleRemove"
:before-upload="beforeUpload"
:limit="upload_number"
:limit="uploadLimitNumber"
:accept="fileAccept"
:multiple="true"
>
@@ -31,7 +31,7 @@
:on-remove="handleRemove"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
:limit="upload_number"
:limit="uploadLimitNumber"
:accept="fileAccept"
:multiple="true"
list-type="picture-card"
@@ -71,7 +71,7 @@
:on-remove="handleRemove"
:on-exceed="handleExceed"
:before-upload="beforeUpload"
:limit="upload_number"
:limit="uploadLimitNumber"
:accept="fileAccept"
class="drag-upload"
:class="{ 'drag-upload-disabled': uploadMode !== 'pc' }"
@@ -123,7 +123,7 @@ module.exports = {
},
// 上传数量
upload_number: {
type: Number,
type: [Number, String],
default: 1,
required: false
},
@@ -198,8 +198,12 @@ module.exports = {
action() {
return this.upload_result_type === "id" ? this.upload_return_id_api : this.upload_dynamic_return_url_api
},
uploadLimitNumber() {
const number = Number(this.upload_number)
return Number.isFinite(number) && number > 0 ? number : 1
},
upload_tips() {
const uploadNumber = this.upload_number
const uploadNumber = this.uploadLimitNumber
const fileAccept = this.fileAccept
const fileSize = this.upload_size
@@ -359,10 +363,12 @@ module.exports = {
handleRemove(file, fileList) {
if (this.upload_result_category === "interval") {
if (this.upload_result_type === "id") {
}
if (this.upload_result_type === "url") {
}
const resultIntervalValue = fileList
.map((item) => item.response && item.response.data ? item.response.data : item.url || item.data)
.filter((item) => item)
.join(",")
this.$emit("update:value", resultIntervalValue)
return
}
if (this.upload_result_category === "array") {
@@ -381,13 +387,11 @@ module.exports = {
)
}
}
} else {
this.$emit("update:value", fileList)
}
},
handleExceed(files, fileList) {
this.$message.warning("最多只能上传" + this.upload_number + "个文件")
this.$message.warning("最多只能上传" + this.uploadLimitNumber + "个文件")
},
// 上传事件
@@ -562,8 +566,8 @@ module.exports = {
const originFileList = this.fileList || []
console.log(data.files)
if (this.upload_number) {
if (originFileList.length + data.files.length > this.upload_number) {
if (this.uploadLimitNumber) {
if (originFileList.length + data.files.length > this.uploadLimitNumber) {
this.$message.error("上传文件数量超出限制")
return
}
@@ -56,6 +56,7 @@
<script src="${base!}/components/plugins/sysDict/DictData.js"></script>
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
<script src="${base!}/assets/platform/js/util/coordinateUtil.js"></script>
<script src="${base!}/assets/platform/js/util/voiceMenuNavigator.js"></script>
<script src="${base!}/assets/platform/js/main.js"></script>
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
@@ -167,10 +168,20 @@
func && typeof func === "function" && func()
}
function returnH5Home(activeTab = "home") {
if (window.store && typeof window.store.commit === "function") {
window.store.commit("setActiveTarBar", activeTab || "home")
}
window.location.replace("/platform/h5/home")
}
Vue.mixin({
methods: {
historyBack: function(func = ()=>{}) {
historyBack(func);
},
returnH5Home: function(activeTab = "home") {
returnH5Home(activeTab)
}
},
created() {
@@ -397,19 +408,49 @@
</script>
<script nonce="${cspNonce!}">
Vue.component("rich-text", httpVueLoader("/components/plugins/sysRichTextView/index.vue?v=" + new Date().getTime()))
Vue.component("h5-file-upload", httpVueLoader("/components/plugins/sysUpload/h5Index.vue?v=" + new Date().getTime()))
Vue.component("h5-signature", httpVueLoader("/components/plugins/sysSignature/h5Index.vue?v=" + new Date().getTime()))
Vue.component("text-editor", httpVueLoader("/components/plugins/sysTextEditor/index.vue?v=" + new Date().getTime()))
Vue.component("dict-tag", httpVueLoader("/components/plugins/sysDict/DictTag.vue?v=" + new Date().getTime()))
Vue.component("svg-icon", httpVueLoader("/components/plugins/sysSvgIcon/index.vue?v=" + new Date().getTime()))
Vue.component("year-van-dropdown-item", httpVueLoader("/components/plugins/vantMore/yearVanDropDownItem.vue?v=" + new Date().getTime()))
Vue.component("van-text-dialog", httpVueLoader("/components/plugins/vantMore/vantTextDialog.vue?v=" + new Date().getTime()))
Vue.component("enum-tag", httpVueLoader("/components/plugins/sysEnum/EnumTag.vue?v=" + new Date().getTime()))
Vue.component("table-list", httpVueLoader("/components/plugins/h5/TableList.vue?v=" + new Date().getTime()))
Vue.component("table-column", httpVueLoader("/components/plugins/h5/TableColumn.vue?v=" + new Date().getTime()))
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/H5Index.vue?v=" + new Date().getTime()))
const h5ComponentVersion = "20260524"
Vue.component("rich-text", httpVueLoader("/components/plugins/sysRichTextView/index.vue?v=" + h5ComponentVersion))
Vue.component("h5-file-upload", httpVueLoader("/components/plugins/sysUpload/h5Index.vue?v=" + h5ComponentVersion))
Vue.component("h5-signature", httpVueLoader("/components/plugins/sysSignature/h5Index.vue?v=" + h5ComponentVersion))
Vue.component("text-editor", httpVueLoader("/components/plugins/sysTextEditor/index.vue?v=" + h5ComponentVersion))
Vue.component("dict-tag", httpVueLoader("/components/plugins/sysDict/DictTag.vue?v=" + h5ComponentVersion))
Vue.component("svg-icon", httpVueLoader("/components/plugins/sysSvgIcon/index.vue?v=" + h5ComponentVersion))
Vue.component("year-van-dropdown-item", httpVueLoader("/components/plugins/vantMore/yearVanDropDownItem.vue?v=" + h5ComponentVersion))
Vue.component("van-text-dialog", httpVueLoader("/components/plugins/vantMore/vantTextDialog.vue?v=" + h5ComponentVersion))
Vue.component("enum-tag", httpVueLoader("/components/plugins/sysEnum/EnumTag.vue?v=" + h5ComponentVersion))
Vue.component("table-list", httpVueLoader("/components/plugins/h5/TableList.vue?v=" + h5ComponentVersion))
Vue.component("table-column", httpVueLoader("/components/plugins/h5/TableColumn.vue?v=" + h5ComponentVersion))
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/H5Index.vue?v=" + h5ComponentVersion))
</script>
<style>
.h5-voice-menu {
position: fixed;
right: 16px;
bottom: calc(76px + env(safe-area-inset-bottom));
z-index: 21000;
width: 48px;
height: 48px;
border: 0;
border-radius: 50%;
color: #fff;
background: #1989fa;
box-shadow: 0 6px 18px rgba(25, 137, 250, 0.35);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
outline: none;
}
.h5-voice-menu.is-listening {
background: #ff976a;
box-shadow: 0 6px 18px rgba(255, 151, 106, 0.38);
}
.h5-voice-menu .voice-menu-text {
display: none;
}
</style>
</head>
<body>
<div class="main-page page-showtabbar">
@@ -417,6 +458,10 @@
<div id="container">${layoutContent}</div>
</div>
</div>
<button type="button" class="h5-voice-menu" id="voice-menu-btn" data-platform="H5" title="语音打开菜单">
<i class="fa fa-microphone"></i>
<span class="voice-menu-text">语音</span>
</button>
<script nonce="${cspNonce!}">
toggleShowBar()
</script>
@@ -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>
@@ -0,0 +1,164 @@
<!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, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"/>
<title>智慧工会</title>
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vant/index.css?v=20260524"/>
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css?v=20260524"/>
<link rel="stylesheet" href="${base!}/assets/platform/css/h5.css?v=20260524"/>
<script src="${base!}/assets/platform/plugins/vue/vue.js?v=20260524"></script>
<script src="${base!}/assets/platform/plugins/vant/index.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 = true
if (vant.Form && vant.Form.props && vant.Form.props.showErrorMessage) {
vant.Form.props.showErrorMessage.default = false
}
if (vant.ActionSheet && vant.ActionSheet.props && vant.ActionSheet.props.lazyRender) {
vant.ActionSheet.props.lazyRender.default = false
}
vant.Toast.allowMultiple()
if (vant.Lazyload) {
Vue.use(vant.Lazyload)
}
window.loadScriptOnce = function(src) {
window.__scriptLoadingMap = window.__scriptLoadingMap || {}
if (window.__scriptLoadingMap[src]) {
return window.__scriptLoadingMap[src]
}
window.__scriptLoadingMap[src] = new Promise(function(resolve, reject) {
const existing = document.querySelector('script[src="' + src + '"]')
if (existing) {
resolve()
return
}
const script = document.createElement("script")
script.src = src
script.onload = function() {
resolve()
}
script.onerror = function() {
reject(new Error("script load failed: " + src))
}
document.head.appendChild(script)
})
return window.__scriptLoadingMap[src]
}
window.loadStyleOnce = function(href) {
window.__styleLoadingMap = window.__styleLoadingMap || {}
if (window.__styleLoadingMap[href]) {
return window.__styleLoadingMap[href]
}
window.__styleLoadingMap[href] = new Promise(function(resolve, reject) {
const existing = document.querySelector('link[href="' + href + '"]')
if (existing) {
resolve()
return
}
const link = document.createElement("link")
link.rel = "stylesheet"
link.href = href
link.onload = function() {
resolve()
}
link.onerror = function() {
reject(new Error("style load failed: " + href))
}
document.head.appendChild(link)
})
return window.__styleLoadingMap[href]
}
const store = {
state: {
user: JSON.parse(window.sessionStorage.getItem("user") || "null"),
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
activeTarBar: null
},
commit(type, payload) {
if (type === "setUser") {
this.state.user = payload
} else if (type === "setActiveTarBar") {
this.state.activeTarBar = payload
}
},
dispatch(type) {
if (type === "logout") {
window.localStorage.removeItem("zhgh-h5-vuex")
window.location.href = "/platform/login/logout"
}
}
}
window.ELEMENT = window.ELEMENT || {
Message: {
error(message) {
vant.Toast.fail(message || "操作失败")
}
},
Loading: {
service() {
const loading = vant.Toast.loading({
duration: 0,
forbidClick: true,
message: "处理中,请稍候"
})
return {
close() {
loading.clear()
}
}
}
}
}
function historyBack(func) {
if (window.history.length > 1) {
window.history.back()
} else {
window.location.href = "/platform/h5/home"
}
func && typeof func === "function" && func()
}
function returnH5Home(activeTab = "home") {
store.commit("setActiveTarBar", activeTab || "home")
window.location.replace("/platform/h5/home")
}
Vue.mixin({
methods: {
historyBack: function(func = function(){}) {
historyBack(func)
},
returnH5Home: function(activeTab = "home") {
returnH5Home(activeTab)
}
}
})
Vue.prototype.$commonUtil = commonUtil
Vue.prototype.$auth = commonUtil.authService()
Vue.prototype.$axios = commonUtil.axiosService()
Vue.prototype.$downLoad = commonUtil.downLoadService.bind(commonUtil)
</script>
</head>
<body>
<div class="main-page">
<div class="page-wrapper">
<div id="container">${layoutContent}</div>
</div>
</div>
</body>
</html>
@@ -709,7 +709,7 @@
</div>
<div class="v4-user-section">
<button type="button" class="v4-voice-menu" id="voice-menu-btn" title="语音打开菜单">
<button type="button" class="v4-voice-menu" id="voice-menu-btn" data-platform="PC" title="语音打开菜单">
<i class="fa fa-microphone"></i>
<span class="voice-menu-text">语音</span>
</button>
@@ -1513,7 +1513,7 @@ layout("/layouts/v4/baseLayout.html"){
<div class="oa-hero-section">
<!-- 背景图 -->
<div class="oa-background-image">
<img src="${config.AppHomeImg!}" alt=""/>
<img :src="homeBannerList[0] || ''" alt=""/>
</div>
<!-- 搜索和统计区域 -->
@@ -2020,6 +2020,7 @@ layout("/layouts/v4/baseLayout.html"){
recommendServices: [],
favoriteItems: [],
recommendApps: [],
homeBannerList: "${config.AppHomeImg!}".split(",").map(item => item.trim()).filter(item => item),
// 工会网站新闻
websiteNews: {},
@@ -73,7 +73,7 @@ layout("/layouts/v4/baseLayout.html"){
<!-- 首页banner -->
<div class="section-banner">
<div class="banner-img">
<img src="${config.AppHomeImg!}" alt=""/>
<img :src="homeBannerList[0] || ''" alt=""/>
<stats></stats>
</div>
</div>
@@ -87,6 +87,8 @@ layout("/layouts/v4/baseLayout.html"){
</div>
</div>
<work-template v-if="$auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN'])"></work-template>
<jcdt :list="websiteNews"></jcdt>
</div>
@@ -94,6 +96,7 @@ layout("/layouts/v4/baseLayout.html"){
<script nonce="${cspNonce!}">
<!--#include("act.js"){}#-->
<!--#include("template.js"){}#-->
<!--#include("jcdt.js"){}#-->
<!--#include("entry.js"){}#-->
<!--#include("user.js"){}#-->
@@ -104,11 +107,13 @@ layout("/layouts/v4/baseLayout.html"){
el: '#v4-home-app',
data(){
return{
websiteNews: []
websiteNews: [],
homeBannerList: "${config.AppHomeImg!}".split(",").map(item => item.trim()).filter(item => item)
}
},
components: {
'act': act,
'work-template': workTemplate,
'jcdt': jcdt,
'entry': entry,
'user': user,
@@ -0,0 +1,206 @@
const workTemplate = {
template: /*language=HTML*/ `
<div class="section-wrapper work-template-wrapper">
<div class="work-template-header">
<div class="work-template-title">
<i class="fa fa-clipboard"></i>
<span>工作模板</span>
<em>/Templates</em>
</div>
</div>
<div class="work-template-card">
<div v-if="templateList && templateList.length > 0" class="work-template-grid">
<div class="work-template-item"
v-for="template in templateList"
:key="template.id"
@click="go(template)">
<div class="work-template-icon">
<img :src="template.templateIcon || template.cover || 'https://www.ncu.edu.cn/__local/8/52/2C/644986B4C9A030F7B65300178A6_8679CB08_18467.jpg'"
alt="">
</div>
<div class="work-template-name">{{ template.templateName || template.name }}</div>
</div>
</div>
<div v-else class="no-activity-placeholder">
<div class="icon"><i class="fa fa-clipboard"></i></div>
<p>暂无工作模板</p>
<p class="subtext">敬请关注后续更新</p>
</div>
</div>
</div>
`,
data() {
return {
templateList: []
}
},
methods: {
listTemplate() {
this.$axios.post("/platform/home/listHomeTemplate").then((res) => {
if (res.code === 0) {
this.templateList = res.data
}
})
},
go(template) {
if (!template.url) {
this.$message.warning("管理员未配置模板链接")
return
}
const url = new URL(template.url, window.location.origin)
if (!url.searchParams.get("mode")) {
url.searchParams.set("mode", "edit")
}
if (!url.searchParams.get("id") && template.id) {
url.searchParams.set("id", template.id)
}
window.localStorage.setItem("zhgh_home_template_edit", JSON.stringify({
path: url.pathname,
id: url.searchParams.get("id"),
expiresAt: Date.now() + 60000
}))
window.open(url.href)
}
},
mounted() {
this.listTemplate()
},
style: /*language=CSS*/ `
.work-template-wrapper {
width: 80%;
margin: 20px auto 28px;
}
.work-template-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.work-template-title {
display: inline-flex;
align-items: baseline;
gap: 6px;
color: #19324d;
font-size: 18px;
font-weight: 700;
}
.work-template-title i {
color: #409eff;
font-size: 16px;
}
.work-template-title em {
color: #b5c0d6;
font-size: 14px;
font-style: normal;
font-weight: 500;
}
.work-template-card {
background: #ffffff;
border: 1px solid #edf1f7;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
min-height: 116px;
padding: 18px 20px;
}
.work-template-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
justify-items: center;
justify-content: start;
gap: 12px 18px;
overflow-x: hidden;
}
.work-template-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 8px;
background: #fff;
width: 100%;
max-width: 100%;
padding: 6px;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.work-template-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.work-template-icon {
width: 46px;
height: 46px;
margin-bottom: 8px;
}
.work-template-icon img {
width: 100%;
height: 100%;
object-fit: contain;
}
.work-template-name {
min-height: 34px;
font-size: 12px;
color: #333;
line-height: 1.4;
word-break: break-word;
text-align: center;
}
.work-template-wrapper .no-activity-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
color: #94a3b8;
text-align: center;
}
.work-template-wrapper .no-activity-placeholder .icon {
font-size: 44px;
margin-bottom: 12px;
opacity: 0.6;
}
.work-template-wrapper .no-activity-placeholder p {
margin: 4px 0 0;
font-size: 16px;
font-weight: 500;
}
.work-template-wrapper .no-activity-placeholder .subtext {
font-size: 13px;
opacity: 0.8;
}
@media (max-width: 768px) {
.work-template-grid {
grid-template-columns: repeat(3, minmax(72px, 1fr));
gap: 12px;
}
.work-template-item {
padding: 8px 4px;
}
}
@media (max-width: 480px) {
.work-template-grid {
grid-template-columns: repeat(2, minmax(72px, 1fr));
gap: 12px;
}
}
`
};
@@ -75,8 +75,9 @@ layout("/layouts/platform.html"){
></el-input>
</el-form-item>
<el-form-item prop="configValue" label="参数值">
<template v-if="formData.configKey === 'AppLogo' || formData.configKey === 'AppHomeImg'">
<template v-if="formData.configKey === 'AppLogo'">
<file-upload
key="AppLogo"
style="--upload-width: 214px;--upload-height:64px"
:value.sync="formData.configValue"
:upload_number="1"
@@ -84,8 +85,43 @@ layout("/layouts/platform.html"){
upload_result_category="interval"
upload_result_type="url"
></file-upload>
<span v-if="formData.configKey === 'AppLogo'">最佳分辨率:428 x 128</span>
<span v-else-if="formData.configKey === 'AppHomeImg'">最佳分辨率:2756 x 732</span>
<span>最佳分辨率:428 x 128</span>
</template>
<template v-else-if="formData.configKey === 'AppHomeImg'">
<file-upload
key="AppHomeImg"
style="--upload-width: 214px;--upload-height:64px"
:value.sync="formData.configValue"
:upload_number="10"
upload_mode="image"
upload_result_category="interval"
upload_result_type="url"
></file-upload>
<span>最佳分辨率:2756 x 732,最多可上传 10 张,首页按上传顺序轮播</span>
</template>
<template v-else-if="formData.configKey === 'AppFeaturedActivityImg'">
<file-upload
key="AppFeaturedActivityImg"
style="--upload-width: 214px;--upload-height:96px"
:value.sync="formData.configValue"
:upload_number="1"
upload_mode="image"
upload_result_category="interval"
upload_result_type="url"
></file-upload>
<span>最佳分辨率:750 x 280,精彩活动页顶部图片</span>
</template>
<template v-else-if="formData.configKey === 'AppFestivalBenefitImg'">
<file-upload
key="AppFestivalBenefitImg"
style="--upload-width: 214px;--upload-height:96px"
:value.sync="formData.configValue"
:upload_number="1"
upload_mode="image"
upload_result_category="interval"
upload_result_type="url"
></file-upload>
<span>最佳分辨率:750 x 280,节日福利页顶部图片</span>
</template>
<template v-else>
<div v-if="'true'===formData.configValue||'false'===formData.configValue">
@@ -179,6 +215,7 @@ layout("/layouts/platform.html"){
doEdit: function () {
var self = this
var url = base + "/platform/sys/conf/editDo"
self.normalizeConfigValue()
self.$refs["editForm"].validate(function (valid) {
if (valid) {
$.post(
@@ -204,6 +241,41 @@ layout("/layouts/platform.html"){
}
})
},
normalizeConfigValue: function () {
if (Array.isArray(this.formData.configValue)) {
this.formData.configValue = this.formData.configValue
.map(function (item) {
if (typeof item === "string") {
return item
}
if (item && item.response && item.response.data) {
return item.response.data
}
if (item && item.url) {
return item.url
}
if (item && item.data) {
return item.data
}
return ""
})
.filter(function (item) {
return item
})
.join(",")
}
if (typeof this.formData.configValue === "string") {
this.formData.configValue = this.formData.configValue
.split(",")
.map(function (item) {
return item.trim()
})
.filter(function (item) {
return item
})
.join(",")
}
},
pageOrder: function (column) {
//按字段排序
this.pageForm.pageOrderName = column.prop
@@ -0,0 +1,193 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="名称" prop="name">
<el-input v-model="pageForm.name" clearable placeholder="请输入名称"></el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never">
<el-table :data="tableData" header-align="center" border style="width: 100%">
<el-table-column label="序号" type="index" width="50px" :index="indexMethod"></el-table-column>
<el-table-column label="模板名称" prop="templateName" width="260px">
<template slot-scope="scope">
<el-input
v-model="scope.row.templateName"
size="mini"
clearable
placeholder="请输入模板名称"
@change="updateTemplateName(scope.row)">
</el-input>
</template>
</el-table-column>
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="PC端链接" prop="url" show-overflow-tooltip></el-table-column>
<el-table-column label="模板图标" prop="templateIcon" width="120px" align="center">
<template slot-scope="scope">
<file-upload
class="template-icon-upload"
:upload_number="1"
:value="scope.row.templateIcon"
accept=".jpg,.jpeg,.png"
upload_result_type="url"
upload_result_category="interval"
upload_mode="image"
@update:value="onTemplateIconChange(scope.row, $event)">
</file-upload>
</template>
</el-table-column>
<el-table-column label="是否置顶" prop="top" width="100px">
<template slot-scope="scope">
<el-tag size="mini" v-if="scope.row.top" type="success"></el-tag>
<el-tag size="mini" v-else type="info"></el-tag>
</template>
</el-table-column>
<el-table-column label="关联类路径" prop="classPath" show-overflow-tooltip></el-table-column>
<el-table-column label="状态" prop="enable" width="80px">
<template slot-scope="scope">
<i v-if="!scope.row.enable" class="fa fa-circle text-danger ml5"></i>
<i v-else class="fa fa-circle text-success ml5"></i>
</template>
</el-table-column>
<el-table-column label="操作" width="180px">
<template slot-scope="scope">
<el-link type="danger" size="mini" @click="disable(scope.row.id)" v-if="scope.row.enable">关闭</el-link>
<el-link type="primary" size="mini" @click="enable(scope.row.id)" v-if="!scope.row.enable">开启</el-link>
<el-link type="primary" size="mini" @click="topUp(scope.row.id)" v-if="!scope.row.top">置顶</el-link>
<el-link type="danger" size="mini" @click="cancelTopUp(scope.row.id)" v-if="scope.row.top">取消置顶</el-link>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {}
},
methods: {
disable(id) {
this.$confirm("您确定要关闭吗?", "提示", {
type: "warning",
confirmButtonText: "确定",
cancelButtonText: "取消"
}).then(() => {
this.$axios.post(loc() + "/disable", {id}).then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.pageData()
}
})
})
},
enable(id) {
this.$confirm("您确定要开启吗?", "提示", {
type: "warning",
confirmButtonText: "确定",
cancelButtonText: "取消"
}).then(() => {
this.$axios.post(loc() + "/enable", {id}).then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.pageData()
}
})
})
},
topUp(id) {
this.$confirm("您确定要置顶吗?", "提示", {
type: "warning",
confirmButtonText: "确定",
cancelButtonText: "取消"
}).then(() => {
this.$axios.post(loc() + "/topUp", {id}).then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.pageData()
}
})
})
},
cancelTopUp(id) {
this.$confirm("您确定要取消置顶吗?", "提示", {
type: "warning",
confirmButtonText: "确定",
cancelButtonText: "取消"
}).then(() => {
this.$axios.post(loc() + "/cancelTopUp", {id}).then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.pageData()
}
})
})
},
updateTemplateName(row) {
this.$axios.post(loc() + "/updateTemplateName", {
id: row.id,
templateName: row.templateName
}).then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
} else {
this.$message.error(resp.msg)
}
})
},
onTemplateIconChange(row, templateIcon) {
if (templateIcon === undefined && !row.templateIcon) {
return
}
row.templateIcon = templateIcon
this.updateTemplateIcon(row)
},
updateTemplateIcon(row) {
this.$nextTick(() => {
this.$axios.post(loc() + "/updateTemplateIcon", {
id: row.id,
templateIcon: row.templateIcon
}).then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
} else {
this.$message.error(resp.msg)
}
})
})
}
},
created() {
this.pageData()
}
})
</script>
<style>
.template-icon-upload {
--upload-width: 48px;
--upload-height: 48px;
}
.template-icon-upload .el-upload-list--picture-card .el-upload-list__item,
.template-icon-upload .el-upload--picture-card {
width: 48px !important;
height: 48px !important;
line-height: 48px !important;
margin: 0;
}
.template-icon-upload .el-upload--picture-card i {
font-size: 18px;
}
</style>
<!--#
}
#-->
@@ -949,39 +949,44 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
return data
},
async init(id) {
if (id) {
this.activeName = "1"
this.findOne(id)
} else {
this.formData = {
time: [],
applyTime2: [],
plannedDate: [],
goods: [],
billFiles: [],
photoFiles: [],
otherFiles: [],
tissuePersonList: [],
unionUserNumberLimit: [],
signUpMethod: null,
rangerMeter: 100,
isEnrollSystem: true,
isPushHome: 0,
isIntegral: false
this.formLoading = true
try {
if (id) {
this.activeName = "1"
await this.findOne(id)
} else {
this.formData = {
time: [],
applyTime2: [],
plannedDate: [],
goods: [],
billFiles: [],
photoFiles: [],
otherFiles: [],
tissuePersonList: [],
unionUserNumberLimit: [],
signUpMethod: null,
rangerMeter: 100,
isEnrollSystem: true,
isPushHome: 0,
isIntegral: false
}
this.generateActivityCode().then((data) => {
this.$set(this.formData, "activityCode", data)
})
this.getUnionData().then((data) => {
this.$set(this.formData, "unionUserNumberLimit", data)
})
}
this.generateActivityCode().then((data) => {
this.$set(this.formData, "activityCode", data)
})
this.getUnionData().then((data) => {
this.$set(this.formData, "unionUserNumberLimit", data)
this.clubOptions = await this.getClubsByRole()
this.clubOptions.map((v) => {
v.name = v.clubName
})
const units = await this.$businessTool.listUnit()
this.unitOptions = this.clubOptions.concat(units)
} finally {
this.formLoading = false
}
this.clubOptions = await this.getClubsByRole()
this.clubOptions.map((v) => {
v.name = v.clubName
})
const units = await this.$businessTool.listUnit()
this.unitOptions = this.clubOptions.concat(units)
},
async queryUserByIds(ids) {
const {data} = await this.$axios.post("/platform/activity/culture/applyActivity/queryUserByIds", {ids: JSON.stringify(ids)})
@@ -1018,6 +1023,14 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
this.bizId = row.id
this.activeName = "1"
this.init(row.id)
},
openUrlEdit() {
const params = new URLSearchParams(window.location.search)
const mode = params.get("mode")
const id = params.get("id")
if (mode === "edit" && id) {
this.openEdit({id})
}
}
},
async created() {
@@ -1030,5 +1043,8 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
})
this.getActivityGroup()
// this.init()
},
mounted() {
this.openUrlEdit()
}
}
@@ -112,7 +112,18 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
撤回
</el-dropdown-item>
<el-dropdown-item :command="{type:'delete',row}"
<el-dropdown-item
v-if="activity_type === 40001 && $auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN']) && !row.isTemplate"
:command="{type:'setTemplate',row}">
设为模板
</el-dropdown-item>
<el-dropdown-item
v-if="activity_type === 40001 && $auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN']) && row.isTemplate"
:command="{type:'cancelTemplate',row}">
取消模板
</el-dropdown-item>
<el-dropdown-item :command="{type:'delete',row}" :disabled="!!row.isTemplate"
v-if="(activity_type === 40001 && (row.taskKey === 'startTask' || !row.instanceId)) || (activity_type !== 40001 && $auth.hasRole('SYSADMIN'))" >
删除
</el-dropdown-item>
@@ -209,6 +220,10 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
this.onRevoke(row)
} else if (type === "delete") {
this.doDelete(row)
} else if (type === "setTemplate") {
this.setTemplate(row)
} else if (type === "cancelTemplate") {
this.cancelTemplate(row)
} else if (type === "openCode") {
this.url = location.origin + "/platform/h5/activity/culture/signUp?id=" + row.id + "&groupId=" + row.groupId
this.codeDialogVisible = true
@@ -231,6 +246,10 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
})
},
doDelete(row) {
if (row.isTemplate) {
this.$message.warning("该活动已设为模板,请先取消模板后再删除")
return
}
const {id} = row
this.$confirm("确定要删除该活动吗?", "提示", {type: "warning"}).then(async () => {
this.$set(row, "loading", true)
@@ -244,6 +263,40 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
this.$set(row, "loading", false)
})
},
setTemplate(row) {
this.$confirm("确定将该活动设为工作模板吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
this.$set(row, "loading", true)
const resp = await this.$axios.post("/platform/activity/culture/infoManage/setTemplate", {id: row.id})
if (resp.code === 0) {
this.doSearch()
this.$message.success(resp.msg)
} else {
this.$message.error(resp.msg)
}
this.$set(row, "loading", false)
})
},
cancelTemplate(row) {
this.$confirm("确定取消该活动的工作模板吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
this.$set(row, "loading", true)
const resp = await this.$axios.post("/platform/activity/culture/infoManage/cancelTemplate", {id: row.id})
if (resp.code === 0) {
this.doSearch()
this.$message.success(resp.msg)
} else {
this.$message.error(resp.msg)
}
this.$set(row, "loading", false)
})
},
onRevoke(row) {
this.$confirm("您确定要撤回吗?", "提示", {
confirmButtonText: "确定",
@@ -268,7 +321,8 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
}
})
this.tableLoading = false
}
},
},
async created() {
// 根据 activity_type 动态添加流程相关列
@@ -279,5 +333,5 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
);
}
this.pageData()
}
},
}
@@ -656,7 +656,7 @@
async applyUserModelInput(val) {
if (val === 2) {
if (!this.eventForm.unionLimit || this.eventForm.unionLimit.length === 0) {
const resp = await $.get(loc() + "/getUnionLimit", { activityScopeId: this.eventForm.activityGroupId })
const resp = await $.get("/platform/activity/sports/info/mange/getUnionLimit", { activityScopeId: this.eventForm.activityGroupId })
if (resp.code === 0) {
this.$set(this.eventForm, "unionLimit", resp.data)
}
@@ -736,7 +736,7 @@
spinner: "el-icon-loading",
background: "rgba(0,0,0,.45)"
})
const resp = await this.$axios.post(loc() + "/doAssignmentDate", {
const resp = await this.$axios.post("/platform/activity/sports/info/mange/doAssignmentDate", {
eventIds: JSON.stringify(this.checkedEventInfo.map((v) => v.id)),
events: JSON.stringify(this.checkedEventInfo)
})
@@ -1043,7 +1043,7 @@
},
async operate() {
const deleteEventIds = this.eventsIds.filter((v) => !this.formData.eventsIds.includes(v))
const { data } = await $.get(loc() + "/getEventApply", {
const { data } = await $.get("/platform/activity/sports/info/mange/getEventApply", {
activityId: this.formData.id,
eventId: JSON.stringify(deleteEventIds)
})
@@ -0,0 +1,77 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<div slot="header">
<div class="sports-new-actions">
<el-button @click="doSave" type="primary">保 存</el-button>
<el-button @click="doOperate" type="primary">提 交</el-button>
<!--<el-button @click="back">返 回</el-button>-->
</div>
</div>
<add-activity
:is_a06="$auth.hasRole('SCHOOL_UNION_ADMIN')"
:is_club="$auth.hasRole('CLUB_PRESIDENT')"
:is_sysadmin="$auth.hasRole('SYSADMIN')"
ref="addActivity"
@flip="flip"
@flush="flush">
</add-activity>
</el-card>
</div>
<script nonce="${cspNonce!}">
<!--#include("/platform/zhgh/activity/sports/infoManage/addActivity.js"){}#-->
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
components: {
"add-activity": ACTIVITY_SPORTS_ADD_ACTIVITY
},
methods: {
back() {
commonUtil.pjaxPush("/platform/activity/sports/info/mange")
},
flip() {
this.back()
},
flush() {
},
async doOperate() {
await this.$refs.addActivity.operate()
},
async doSave() {
await this.$refs.addActivity.doSave()
},
initForm() {
const mode = GetQueryString("mode")
const id = GetQueryString("id")
if (mode === "edit" && id) {
this.$refs.addActivity.openEdit({id}, true)
} else {
this.$refs.addActivity.openAdd()
}
}
},
mounted() {
this.$nextTick(() => {
this.initForm()
})
}
})
</script>
<style>
.sports-new-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>
<!--#
}
#-->
@@ -0,0 +1,58 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.el-drawer__body {
padding: 20px;
}
.my-drawer .el-col-4 {
text-align: right;
}
.my-drawer .el-row {
margin-bottom: 20px;
}
.courseTimeButton {
padding: 0 !important;
}
</style>
<div id="app" v-cloak>
<el-card shadow="never">
<basic-form ref="formRef" @back="back" @refresh="refresh"></basic-form>
</el-card>
</div>
<script nonce="${cspNonce!}">
<!--#include("/platform/zhgh/activity/trainSignUp/manage/basicForm.js"){}#-->
new Vue({
el: "#app",
store,
dicts: ["USER_CAMPUS", "TRAIN_SIGNUP_TYPE"],
components: {
"basic-form": basicForm
},
methods: {
back() {
commonUtil.pjaxPush("/platform/trainSignUp/manage")
},
refresh() {
},
initForm() {
const mode = GetQueryString("mode")
const id = GetQueryString("id")
const row = mode === "edit" && id ? {id} : undefined
this.$refs.formRef.initData(row)
}
},
mounted() {
this.$nextTick(() => {
this.initForm()
})
}
})
</script>
<!--#
}
#-->
@@ -1,5 +1,5 @@
<!--#include('courseTime.js'){}#-->
<!--#include('customForm.js'){}#-->
<!--#include('/platform/zhgh/activity/trainSignUp/manage/courseTime.js'){}#-->
<!--#include('/platform/zhgh/activity/trainSignUp/manage/customForm.js'){}#-->
const basicForm = {
template: /*language=HTML*/ `
<div>
@@ -92,7 +92,13 @@ layout("/layouts/platform.html"){
<el-dropdown-item @click.native="makeCode(row)">签到二维码</el-dropdown-item>
<el-dropdown-item @click.native="onView(row)">查看</el-dropdown-item>
<el-dropdown-item @click.native="openEdit(row)">编辑</el-dropdown-item>
<el-dropdown-item @click.native="onDelete(row)">删除</el-dropdown-item>
<el-dropdown-item
v-if="$auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN']) && !row.isTemplate"
@click.native="setTemplate(row)">设为模板</el-dropdown-item>
<el-dropdown-item
v-if="$auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN']) && row.isTemplate"
@click.native="cancelTemplate(row)">取消模板</el-dropdown-item>
<el-dropdown-item :disabled="!!row.isTemplate" @click.native="onDelete(row)">删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
@@ -154,9 +160,25 @@ layout("/layouts/platform.html"){
],
codeDialogVisible: false,
activityUrl: '',
urlEditInitialized: false,
}
},
methods: {
pageData(data = null) {
const address = this.pageDataUrl ? this.pageDataUrl : loc() + "/pageData"
this.tableLoading = true
return this.$axios.post(address, data ? data : this.pageForm).then((res) => {
this.tableLoading = false
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
this.$nextTick(() => {
this.openUrlEdit()
})
}
return res
})
},
back() {
this.$refs.guava.index()
},
@@ -170,6 +192,25 @@ layout("/layouts/platform.html"){
this.$refs.formRef.initData(row)
})
},
openUrlEdit() {
if (this.urlEditInitialized) {
return
}
const mode = GetQueryString("mode")
const id = GetQueryString("id")
if (mode !== "edit" || !id) {
return
}
if (!this.$refs.guava) {
this.$nextTick(() => {
this.openUrlEdit()
})
return
}
this.urlEditInitialized = true
const row = (this.tableData || []).find(item => item.id === id) || { id }
this.openEdit(row)
},
onView(row) {
this.$refs.guava.view(() => {
this.$refs.infoRef.initData(row.id)
@@ -191,18 +232,56 @@ layout("/layouts/platform.html"){
}
},
async onDelete(row) {
if (row.isTemplate) {
this.$message.warning("该活动已设为模板,请先取消模板后再删除")
return
}
this.$confirm("此操作将永久删除, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
const resp = await this.$axios.post(loc() + "/onDelete", { id: row.id })
this.$message.success(resp.msg)
this.doSearch()
if (resp.code === 0) {
this.$message.success(resp.msg)
this.doSearch()
} else {
this.$message.error(resp.msg)
}
}).catch(() => {})
},
async setTemplate(row) {
this.$confirm("确定将该活动设为工作模板吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
const resp = await this.$axios.post(loc() + "/setTemplate", { id: row.id })
if (resp.code === 0) {
this.$message.success(resp.msg)
this.doSearch()
} else {
this.$message.error(resp.msg)
}
}).catch(() => {})
},
async cancelTemplate(row) {
this.$confirm("确定取消该活动的工作模板吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
const resp = await this.$axios.post(loc() + "/cancelTemplate", { id: row.id })
if (resp.code === 0) {
this.$message.success(resp.msg)
this.doSearch()
} else {
this.$message.error(resp.msg)
}
}).catch(() => {})
},
},
async created() {
mounted() {
this.pageData()
}
})
@@ -242,7 +242,7 @@ let ASSSET_FORM = {
this.searchAssetUseUser(this.formData.assetUseUserId)
},
assetUseUnionIdChange(val) {
const union = this.unions.find(v => v.id === val)
const union = this.unionList.find(v => v.id === val)
if (union) {
this.$set(this.formData, "assetUseUnionName", union.name)
} else {
@@ -260,16 +260,16 @@ let ASSSET_FORM = {
},
// 点击责任人
assetUseUserIdChange(val) {
const {
unionname,
unionid
} = this.assetUseUserOption.find(v => v.id === val)
this.formData.assetUseUnionId = unionid
const user = this.assetUseUserOption.find(v => v.id === val)
if (!user) return
const unionId = user.unionId || user.unionid
const unionName = user.unionName || user.unionname
this.formData.assetUseUnionId = unionId
if (this.formData.assetTypeCode === 1) {
this.formData.assetUseUnionName = "校工会"
} else {
this.formData.assetUseUnionName = unionname
this.formData.assetUseUnionName = unionName
}
},
// 点击开始使用日期🧄预计报废时间
@@ -1,11 +1,19 @@
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-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>
@@ -21,6 +29,28 @@ const form = {
<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="dialogVisible = false">取消</el-button>
@@ -32,8 +62,11 @@ const form = {
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,18 +75,22 @@ const form = {
}
},
methods: {
onOpen(id) {
async onOpen(id) {
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.formData = Object.assign({}, this.currentUserOrg, resp.data, {
mediaFiles: this.parseFiles(resp.data.mediaFiles)
})
}
})
} else {
this.dialogVisible = true
this.formData = {}
this.formData = Object.assign({}, this.currentUserOrg, {
mediaFiles: []
})
this.$nextTick(() => {
this.$refs.formRef.clearValidate()
})
@@ -67,10 +104,41 @@ const form = {
}
})
},
async loadCurrentUserOrg() {
const resp = await $.post("/platform/buildHome/littleHouse/currentUserOrg")
if (resp.code === 0) {
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?.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")
@@ -1,13 +1,12 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<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 +17,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 +25,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)">&#22352;&#26631;</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>
@@ -37,6 +38,7 @@ layout("/layouts/platform.html"){
</guava>
<little-house-form ref="littleHouseFormRef" @refresh="doSearch"></little-house-form>
</div>
<script nonce="${cspNonce!}">
@@ -63,12 +65,78 @@ layout("/layouts/platform.html"){
})
})
},
openCoordinate(row) {
if (!row || !row.id) {
this.$message.warning("\u672a\u83b7\u53d6\u5230\u5c0f\u5bb6\u8bb0\u5f55")
return
}
if (this.hasCoordinate(row)) {
this.$confirm(
"\u5f53\u524d\u8bb0\u5f55\u5df2\u6709\u5750\u6807\uff0c\u662f\u4fee\u6539\u8fd8\u662f\u6e05\u7a7a\uff1f",
"\u63d0\u793a",
{
type: "warning",
distinguishCancelAndClose: true,
confirmButtonText: "\u4fee\u6539\u5750\u6807",
cancelButtonText: "\u6e05\u7a7a\u5750\u6807"
}
).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("\u5750\u6807\u5df2\u6e05\u7a7a")
this.doSearch()
}
})
},
handleCoordinateMessage(event) {
if (event.origin !== window.location.origin) {
return
}
const data = event.data || {}
if (data.type !== "littleHouseCoordinateSaved") {
return
}
this.$message.success("\u5750\u6807\u91c7\u96c6\u6210\u529f")
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">&#36164;&#20135;&#25968;&#25454;</div>
<div ref="usageChart" class="asset-data-chart"></div>
<div class="asset-data-empty" v-if="!chartRows.length">&#26242;&#26080;&#25968;&#25454;</div>
<div class="asset-data-note">&#19981;&#21516;&#39068;&#33394;&#20195;&#34920;&#19981;&#21516;&#20351;&#29992;&#29366;&#20917;</div>
</section>
</main>
<button class="fullscreen-btn" type="button" @click="toggleFullscreen">{{ fullscreen ? "\u9000\u51fa\u5168\u5c4f" : "\u5168\u5c4f\u663e\u793a" }}</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.chart = null
}
},
methods: {
initChart() {
if (!window.echarts || !this.$refs.usageChart) {
return
}
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.chartRows = (data.states || [])
.map(item => ({
name: item.name || "\u672a\u586b\u5199",
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\u4ef6" : "",
fill: "#1683f4",
fontSize: 15,
fontWeight: 700,
textAlign: "center"
}
},
series: [{
name: "\u4f7f\u7528\u72b6\u51b5",
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.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">&#22522;&#23618;&#24037;&#20250;</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>&#20154;</span></div>
</div>
</div>
<div class="base-union-empty" v-else>&#26242;&#26080;&#25968;&#25454;</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.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.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">&#21327;&#20250;&#31038;&#22242;</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>&#20154;</span></div>
</div>
</div>
<div class="club-empty" v-else>&#26242;&#26080;&#25968;&#25454;</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.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.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">&#24944;&#38382;&#27719;&#24635;</div>
<div class="condolence-chart">
<div class="condolence-empty" v-if="!hasData">&#26242;&#26080;&#25968;&#25454;</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.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.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">&#25968;&#25454;&#25351;&#26631;</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: "\u7597\u4f11\u517b\u4eba\u6570", value: this.displayNumber(this.dataMetric.tourCount), unit: "\u4eba", left: "12%", top: "19%"},
{key: "honorCount", label: "\u52b3\u6a21\u5148\u8fdb", value: this.displayNumber(this.dataMetric.honorCount), unit: "\u4eba", left: "88%", top: "19%"},
{key: "difficultCount", label: "\u56f0\u96be\u4eba\u6570", value: this.displayNumber(this.dataMetric.difficultCount), unit: "\u4eba", left: "12%", top: "74%"},
{key: "reimburseTotal", label: "\u62a5\u9500\u603b\u6570", value: this.displayMoneyWan(this.dataMetric.reimburseTotal), unit: "\u4e07\u5143", left: "88%", top: "74%"},
{key: "budgetTotal", label: "\u9884\u7b97\u603b\u989d", value: this.displayMoneyWan(this.dataMetric.budgetTotal), unit: "\u4e07\u5143", 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.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.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.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.session = data.session || {}
this.overview = data.overview || {}
})
},
updateTime() {
const now = new Date()
const week = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"][now.getDay()]
this.currentDate = now.getFullYear() + "-" + this.pad(now.getMonth() + 1) + "-" + this.pad(now.getDate()) + " " + week
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.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">&#25552;&#26696;&#24449;&#38598;&#21644;&#21150;&#29702;</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 ? "\u9000\u51fa\u5168\u5c4f" : "\u5168\u5c4f\u663e\u793a" }}</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: "\u63d0\u6848\u603b\u6570", value: this.displayNumber(this.overview.total), bg: imagePath + "proposal-card-total.png?v=20260526"},
{key: "filed", label: "\u7acb\u6848\u6570\u91cf", value: this.displayNumber(this.overview.filedCount), bg: imagePath + "proposal-card-filed.png?v=20260526"},
{key: "suggestion", label: "\u610f\u89c1\u5efa\u8bae", value: this.displayNumber(this.overview.suggestionCount), bg: imagePath + "proposal-card-suggestion.png?v=20260526"},
{key: "rejected", label: "\u4e0d\u4e88\u7acb\u6848", value: this.displayNumber(this.overview.rejectedCount), bg: imagePath + "proposal-card-rejected.png?v=20260526"},
{key: "done", label: "\u529e\u7ed3\u6570\u91cf", value: this.displayNumber(this.overview.doneCount), bg: imagePath + "proposal-card-done.png?v=20260526"},
{key: "satisfied", label: "\u6ee1\u610f\u7387", 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.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.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">&#32844;&#24037;&#23567;&#23478;</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 ? "\u9000\u51fa\u5168\u5c4f" : "\u5168\u5c4f\u663e\u793a" }}</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.fullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement)
}
}
})
</script>
<!--#
}
#-->

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