logo,壁纸修复,加入小应用管理,样式待调整
build / build-push (push) Successful in 2m11s

This commit is contained in:
chenxing
2026-08-28 14:24:46 +08:00
parent 567c8b7786
commit 759b120f2a
22 changed files with 813 additions and 163 deletions
-2
View File
@@ -52,8 +52,6 @@ yh:
FILE_PATH: /data/upload
FILE_WEB_PATH: http://home.yunhaixingtong.com/userhome/upload/
UPLOAD_WEB_PATH_HTTP: http://home.yunhaixingtong.com/userhome/upload/file/
# 学校配置接口基址(prod 由 Nacos 覆盖)
SCH_CONF_URL: ""
logging:
level:
@@ -42,5 +42,9 @@ public class WebMvcConfig implements WebMvcConfigurer {
// logo 子目录:/upload/logo/** → ${FILE_PATH}/logo/
registry.addResourceHandler("/upload/logo/**")
.addResourceLocations("file:" + basePath + "logo/");
// tempUpload 子目录(应用模板):/upload/tempUpload/** → ${FILE_PATH}/tempUpload/
registry.addResourceHandler("/upload/tempUpload/**")
.addResourceLocations("file:" + basePath + "tempUpload/");
}
}
@@ -0,0 +1,243 @@
package com.yunhai.userhome.controller.api;
import com.yunhai.platform.apistd.BizException;
import com.yunhai.platform.utils.exception.ErrorCode;
import com.yunhai.platform.utils.model.BoaUser;
import com.yunhai.userhome.config.Config;
import com.yunhai.userhome.entity.AppCategory;
import com.yunhai.userhome.entity.UserCurrentRole;
import com.yunhai.userhome.service.IAppCategoryService;
import com.yunhai.userhome.service.IUserCurrentRoleService;
import com.yunhai.userhome.utils.FileUtil;
import com.yunhai.userhome.utils.OtherUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
/**
* 应用设置控制器(自 systemsetting AppSettingController + SysController 迁移,1:1 对齐)。
*
* <p>与原版差异点:写操作(应用 CRUD / UpdateResDate / 模板上传保存)校验调用者
* 为超管(type==1)或校管理员(roleId∈{2,5,7})——原版任何登录用户可写,因新入口仅管理员可见,收紧。
* 查询(分页列表 / FindAppCategory / GetTempList / downloadTemp)登录即可。
*/
@RestController
@RequestMapping(Config.API_PATH)
public class AppSettingController {
@Value("${FILE_PATH:./upload/}")
private String FILE_PATH;
@Value("${FILE_WEB_PATH:}")
private String FILE_WEB_PATH;
@Autowired
private IAppCategoryService appCategoryService;
@Autowired
private IUserCurrentRoleService userCurrentRoleService;
// ==================== 应用类别 CRUD ====================
/** 新增/编辑应用(JSON body,1:1 对齐原 SaveAppCategory) */
@PostMapping(value = "SaveAppCategory", produces = "application/json")
public void saveAppCategory(BoaUser authUserWithDept, @RequestBody AppCategory appCategory) {
checkAdmin(authUserWithDept);
if (appCategory.getCDate() == null) appCategory.setCDate(new Date());
if (appCategory.getResDate() == null) appCategory.setResDate(new Date());
appCategory.setCId(authUserWithDept.getId());
appCategoryService.save(appCategory);
}
@PostMapping(value = "DelAppCategory", produces = "application/json")
public void delAppCategory(BoaUser authUserWithDept, Integer id) {
checkAdmin(authUserWithDept);
appCategoryService.deleteById(id);
}
@GetMapping(value = "GetAppCategoryList")
public Map getAppCategoryList(BoaUser authUserWithDept, Integer page, Integer count,
@RequestParam(value = "roleType") Integer roleType,
@RequestParam(value = "name", required = false) String name) {
Page<AppCategory> data = appCategoryService.findPageList(page, count, roleType, name);
Map map = new HashMap();
map.put("total", data.getTotalElements());
map.put("list", data.getContent());
return map;
}
@GetMapping(value = "FindAppCategory")
public Optional<AppCategory> findAppCategory(BoaUser authUserWithDept, Integer id) {
return appCategoryService.findById(id);
}
@PostMapping(value = "UpdateResDate", produces = "application/json")
public void updateResDate(BoaUser authUserWithDept, Integer id) {
checkAdmin(authUserWithDept);
appCategoryService.updateResDate(id);
}
// ==================== 模板管理(对齐原 SysController) ====================
/** 上传模板压缩包(zip/rar),解压覆盖到 tempUpload/&lt;appModId&gt;/,删除压缩包 */
@PostMapping(value = "tempUpload", produces = "application/json")
public void tempUpload(BoaUser authUserWithDept, @RequestParam("file") MultipartFile file, Integer appModId) {
checkAdmin(authUserWithDept);
if (file == null || file.isEmpty()) {
throw new BizException(ErrorCode.CUSTOM, "请选择上传文件");
}
String originalName = file.getOriginalFilename();
String ext = originalName == null ? "" : originalName.substring(originalName.lastIndexOf(".")).toLowerCase(Locale.ROOT);
if (!".zip".equals(ext) && !".rar".equals(ext)) {
throw new BizException(ErrorCode.CUSTOM, "仅支持 zip/rar 格式");
}
// 不走图片 5MB 校验(模板包允许更大,依赖 multipart 配置),存到 tempUpload 子目录
String uploadPath = FILE_PATH + "/tempUpload/";
File fl = new File(uploadPath);
if (!fl.exists()) fl.mkdirs();
String fileName = FileUtil.uploadFile(file, uploadPath);
String filePath = "/upload/tempUpload/" + fileName;
File unzipFiles = new File(FILE_PATH + "/tempUpload/" + appModId);
if (!unzipFiles.exists()) unzipFiles.mkdirs();
else FileUtil.delAllFile(unzipFiles.getPath());
File zipFile = new File(filePath.replace("/upload", FILE_PATH));
try {
FileUtil.zipDecompress(zipFile, unzipFiles);
} catch (Exception e) {
throw new BizException(ErrorCode.CUSTOM, e.getMessage());
} finally {
zipFile.delete();
}
}
/** 模板文件列表(name + 访问 path) */
@GetMapping(value = "GetTempList")
public List<Map> getTempList(BoaUser authUserWithDept, Integer appModId) {
File sourceFile = new File(FILE_PATH + "/tempUpload/" + appModId);
List<Map> result = new ArrayList<>();
if (sourceFile.exists() && sourceFile.isDirectory() && sourceFile.listFiles() != null) {
Arrays.stream(sourceFile.listFiles()).forEach(file -> {
Map map = new HashMap();
map.put("name", file.getName());
map.put("path", FILE_WEB_PATH + "/tempUpload/" + appModId + "/" + file.getName());
result.add(map);
});
}
return result;
}
/** 保存模板文件内容(新增 fileName 路径穿越校验,原版没有;content 走 JSON body 避免 URL 长度限制) */
@PostMapping(value = "SaveTempFile", produces = "application/json")
public void saveTempFile(BoaUser authUserWithDept, @RequestBody Map<String, Object> body) {
checkAdmin(authUserWithDept);
Object appModIdObj = body.get("appModId");
String fileName = body.get("fileName") == null ? null : body.get("fileName").toString();
String content = body.get("content") == null ? "" : body.get("content").toString();
if (appModIdObj == null) {
throw new BizException(ErrorCode.CUSTOM, "缺少appModId");
}
Integer appModId;
try {
appModId = Integer.parseInt(appModIdObj.toString());
} catch (NumberFormatException e) {
throw new BizException(ErrorCode.CUSTOM, "非法appModId");
}
if (fileName == null || fileName.contains("/") || fileName.contains("\\") || fileName.contains("..")) {
throw new BizException(ErrorCode.CUSTOM, "非法文件名");
}
File dir = new File(FILE_PATH + "/tempUpload/" + appModId);
if (!dir.exists()) dir.mkdirs();
File file = new File(FILE_PATH + "/tempUpload/" + appModId + "/" + fileName);
try {
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
fos.write(content.getBytes());
fos.flush();
fos.close();
} catch (IOException e) {
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "保存失败");
}
}
/** 打包下载模板目录(zip 流式下载,临时 zip 关闭即删) */
@GetMapping(value = "downloadTemp", produces = "application/json;charset=UTF-8")
public ResponseEntity<InputStreamResource> downloadTemp(BoaUser authUserWithDept, Integer appModId, String name) {
File zipFilePath = new File(FILE_PATH + "/tempUpload/" + appModId);
if (!zipFilePath.exists() || !zipFilePath.isDirectory()) {
throw new BizException(ErrorCode.CUSTOM, "模板不存在");
}
String zipName = UUID.randomUUID().toString().substring(0, 8) + ".zip";
File zipFile = new File(FILE_PATH + "/tempUpload/" + zipName);
try {
FileUtil.zipCompress(zipFilePath, zipFile);
} catch (Exception e) {
throw new BizException(ErrorCode.CUSTOM, e.getMessage());
}
String downloadName = (name == null || name.trim().isEmpty()) ? "template" : name;
try {
InputStream inputStream = new FileInputStream(zipFile) {
@Override
public void close() throws IOException {
super.close();
zipFile.delete();
}
};
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Content-Disposition",
"attachment; filename=" + new String((downloadName + ".zip").getBytes("gbk"), "iso8859-1"));
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity.ok().headers(headers)
.contentType(MediaType.parseMediaType("application/octet-stream"))
.body(new InputStreamResource(inputStream));
} catch (IOException e) {
zipFile.delete();
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "下载失败");
}
}
// ==================== 私有方法 ====================
/**
* 写操作权限校验:超管(type==1) 或 校管理员(roleId∈{2,5,7}),其余 403。
*/
private void checkAdmin(BoaUser authUserWithDept) {
if (authUserWithDept.getType() == 1) {
return;
}
Optional<UserCurrentRole> currentRoleOpt = userCurrentRoleService.findById(authUserWithDept.getId());
if (!currentRoleOpt.isPresent() || !OtherUtil.isAdminById(currentRoleOpt.get().getRoleId())) {
throw new BizException(ErrorCode.FORBIDDEN);
}
}
}
@@ -16,8 +16,8 @@ import java.util.Optional;
/**
* 学校设置接口(CAS 登录态,路径 {@code /{platform}/}),对齐旧 UserHome SchSettingsController。
* <p>getSchSettings 实际跨仓调用 schConfig(见 SchSettingsServiceImpl);
* saveSchTheme/saveSchLogo(P1)本期写入本地 sch_settings 表(跨仓回写可后续对接)。 </p>
* <p>getSchSettings 查本地 sch_settings 表(不再依赖跨仓 schConfig);
* saveSchTheme/saveSchLogo 写本地 sch_settings 表。</p>
*/
@RestController
@RequestMapping(value = Config.API_PATH)
@@ -1,13 +1,12 @@
package com.yunhai.userhome.controller.api;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunhai.platform.apistd.BizException;
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
import com.yunhai.platform.utils.enums.ClientEnum;
import com.yunhai.platform.utils.exception.ErrorCode;
import com.yunhai.platform.utils.model.BoaApp;
import com.yunhai.platform.utils.model.BoaAppType;
import com.yunhai.platform.utils.model.BoaGroup;
import com.yunhai.platform.utils.model.BoaUser;
import com.yunhai.userhome.config.Config;
import com.yunhai.userhome.entity.*;
@@ -23,7 +22,6 @@ import com.yunhai.userhome.utils.OtherUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
@@ -36,7 +34,6 @@ import java.util.stream.Collectors;
* <p>待确认接口(已写入待确认清单):
* <ul>
* <li>getUserSettings 中 bschBaseOpenApi.getPcGrzxPage —— 新框架 YschBaseOpenApi 是否有此方法待确认</li>
* <li>GetSchSettingsWallpaper 依赖 SCH_CONF_URL —— 需确认环境地址</li>
* </ul>
*/
@RestController
@@ -49,9 +46,6 @@ public class UserSettingsController {
@Value("${FILE_WEB_PATH:}")
private String FILE_WEB_PATH;
@Value("${SCH_CONF_URL:}")
private String SCH_CONF_URL;
@Autowired
private IUserSettingsService userSettingsService;
@@ -61,21 +55,38 @@ public class UserSettingsController {
@Autowired
private UsedAppService usedAppService;
private final RestTemplate restTemplate = new RestTemplate();
private final ObjectMapper objectMapper = new ObjectMapper();
// ==================== 壁纸 ====================
/**
* 上传壁纸(Header 弹窗与系统设置页共用)。
*
* <p>超管:可选 schId 参数——传 schId&gt;0 归属该校(type=1),不传/0 维持系统级(type=0);
* 校管理员:忽略入参固定本校(type=1);普通用户维持个人壁纸(type=2)。</p>
*/
@PostMapping(value = "wallpaperUpload", produces = "application/json")
public void wallpaperUpload(BoaUser authUserWithDept, @RequestParam("file") MultipartFile file) {
public void wallpaperUpload(BoaUser authUserWithDept, @RequestParam("file") MultipartFile file,
@RequestParam(value = "schId", required = false) Integer schId) {
checkFile(file);
String uploadUrl = this.upload(file, "wallpaper");
UserCurrentRole currentRole = userCurrentRoleService.findById(authUserWithDept.getId())
.orElseThrow(() -> new BizException(ErrorCode.INTERNAL_SERVER_ERROR));
Integer type = authUserWithDept.getType() == 1 ? 0 : OtherUtil.isAdminById(currentRole.getRoleId()) ? 1 : 2;
Integer type;
Integer targetSchId;
if (authUserWithDept.getType() == 1) {
if (schId != null && schId > 0) {
type = 1;
targetSchId = schId;
} else {
// 超管上传的系统壁纸 schId 强制 0,避免切学校身份后产生孤儿数据
type = 0;
targetSchId = 0;
}
} else {
type = OtherUtil.isAdminById(currentRole.getRoleId()) ? 1 : 2;
targetSchId = currentRole.getSchId();
}
Wallpaper wallpaper = new Wallpaper();
// 超管上传的系统壁纸 schId 强制 0,避免切学校身份后产生孤儿数据
wallpaper.setSchId(type == 0 ? 0 : currentRole.getSchId());
wallpaper.setSchId(targetSchId);
wallpaper.setWallpaperUrl(uploadUrl);
wallpaper.setUserId(authUserWithDept.getId());
wallpaper.setType(type);
@@ -98,16 +109,11 @@ public class UserSettingsController {
@PostMapping(value = "checkWallpaper", produces = "application/json")
public void checkWallpaper(BoaUser authUserWithDept, @RequestBody Map res) {
String wallpaperUrl = res.get("wallpaperUrl").toString();
// 校验 URL 属于该用户可见壁纸集合,防止设置任意字符串
// 校验 URL 属于该用户可见壁纸集合(本地 wallpaper 表),防止设置任意字符串
String normalized = normalizeWallpaperUrl(wallpaperUrl);
Set<String> visible = new HashSet<>();
userSettingsService.findWallList(authUserWithDept.getId())
.forEach(w -> visible.add(w.getWallpaperUrl()));
Optional<UserCurrentRole> currentRoleOpt = userCurrentRoleService.findById(authUserWithDept.getId());
Integer schId = currentRoleOpt.isPresent() ? currentRoleOpt.get().getSchId() : null;
if (schId != null) {
getSchSettingsWallpaper(schId).forEach(visible::add);
}
if (!visible.contains(normalized)) {
throw new BizException(ErrorCode.FORBIDDEN, "壁纸不存在或无权使用");
}
@@ -127,35 +133,25 @@ public class UserSettingsController {
map.put("isEdit", model.getIsEdit());
result.add(map);
});
if (authUserWithDept.getType() != 1 && !list.stream().anyMatch(x -> x.getType() == 1)) {
// schId 以当前激活角色为准,忽略前端传参(防传他校 id 看他校壁纸)
Optional<UserCurrentRole> currentRoleOpt = userCurrentRoleService.findById(authUserWithDept.getId());
Integer curSchId = currentRoleOpt.isPresent() ? currentRoleOpt.get().getSchId() : null;
if (curSchId != null) {
List<String> schList = getSchSettingsWallpaper(curSchId);
for (String schWall : schList) {
Map map = new HashMap();
map.put("id", null);
map.put("type", 1);
map.put("url", schWall);
map.put("isEdit", false);
result.add(map);
}
}
}
return result;
}
// ==================== Logo ====================
@PostMapping(value = "logoUpload", produces = "application/json")
public void logoUpload(BoaUser authUserWithDept, @RequestParam("file") MultipartFile file) {
// 仅超管(系统默认)与校管理员(本校)可上传,普通用户无权设置 Logo
public void logoUpload(BoaUser authUserWithDept, @RequestParam("file") MultipartFile file,
@RequestParam(value = "schId", required = false) Integer schId) {
// 仅超管(系统默认/指定学校)与校管理员(本校)可上传,普通用户无权设置 Logo
Integer type;
Integer schId;
Integer targetSchId;
if (authUserWithDept.getType() == 1) {
type = 0;
schId = 0;
if (schId != null && schId > 0) {
type = 1;
targetSchId = schId;
} else {
type = 0;
targetSchId = 0;
}
} else {
UserCurrentRole currentRole = userCurrentRoleService.findById(authUserWithDept.getId())
.orElseThrow(() -> new BizException(ErrorCode.INTERNAL_SERVER_ERROR));
@@ -163,13 +159,13 @@ public class UserSettingsController {
throw new BizException(ErrorCode.FORBIDDEN, "无权设置Logo");
}
type = 1;
schId = currentRole.getSchId();
targetSchId = currentRole.getSchId();
}
checkFile(file);
String uploadUrl = this.upload(file, "logo");
Logo logo = new Logo();
logo.setType(type);
logo.setSchId(schId);
logo.setSchId(targetSchId);
logo.setLogoUrl(uploadUrl);
logo.setUserId(authUserWithDept.getId());
userSettingsService.saveLogo(logo);
@@ -195,6 +191,82 @@ public class UserSettingsController {
userSettingsService.deleteLogoById(id, authUserWithDept);
}
// ==================== 系统设置(学校维度管理视图) ====================
/**
* 机构树(仅超管,403 兜底):YschBaseOpenApi.getGroupTree() → 递归裁剪到学校层
* (对齐原 systemsetting SysController.RecursionGroup:type==1 普通组跳过、type==2 学校组置叶子)。
*/
@GetMapping(value = "getSchTree")
public BoaGroup getSchTree(BoaUser authUserWithDept) {
if (authUserWithDept.getType() != 1) {
throw new BizException(ErrorCode.FORBIDDEN);
}
BoaGroup group = YschBaseOpenApi.getGroupTree();
return recursionGroup(group);
}
/**
* 壁纸管理列表(系统设置页):系统壁纸(type=0) + 目标学校壁纸(type=1),不含个人壁纸(type=2)。
* <p>超管可传任意 schId(不传按 0 即平台默认);校管理员忽略入参固定本校,系统壁纸仅超管可删。</p>
*/
@GetMapping(value = "getSchWallpaperManageList")
public List<Map> getSchWallpaperManageList(BoaUser authUserWithDept, Integer schId) {
List<Map> result = new ArrayList<>();
userSettingsService.findSchWallpaperManageList(authUserWithDept, schId).forEach(model -> {
Map map = new HashMap();
map.put("id", model.getId());
map.put("url", FILE_WEB_PATH + model.getWallpaperUrl().replace("/upload", ""));
map.put("type", model.getType());
map.put("isEdit", model.getIsEdit());
result.add(map);
});
return result;
}
/**
* Logo 管理列表(系统设置页):系统默认(type=0) + 目标学校(type=1)。
* <p>同 getSchWallpaperManageList 权限语义;logo 单张,前端取第一张显示,上传即覆盖。</p>
*/
@GetMapping(value = "getSchLogoManageList")
public List<Map> getSchLogoManageList(BoaUser authUserWithDept, Integer schId) {
List<Map> result = new ArrayList<>();
userSettingsService.findSchLogoManageList(authUserWithDept, schId).forEach(model -> {
Map map = new HashMap();
map.put("id", model.getId());
map.put("url", FILE_WEB_PATH + model.getLogoUrl().replace("/upload", ""));
map.put("type", model.getType());
map.put("isEdit", model.getIsEdit());
result.add(map);
});
return result;
}
/**
* 递归裁剪机构树到学校层(对齐原 systemsetting SysController.RecursionGroup)。
*/
private BoaGroup recursionGroup(BoaGroup group) {
// 组类型:1 普通组 2 学校组 3 年级组 4 班级组 5 区域组 6 学段组 7 教师组 8 学生组
List<BoaGroup> list = new ArrayList<>();
List<BoaGroup> children = group.getChildren();
if (children == null) {
return group;
}
for (BoaGroup boaGroup : children) {
if (boaGroup.getType() == null) continue;
if (boaGroup.getType() == 1) continue;
if (boaGroup.getType() == 2) {
boaGroup.setChildren(null);
list.add(boaGroup);
} else {
BoaGroup model = this.recursionGroup(boaGroup);
list.add(model);
}
}
group.setChildren(list);
return group;
}
// ==================== 用户设置 ====================
@GetMapping(value = "getUserSettings")
@@ -207,9 +279,8 @@ public class UserSettingsController {
// List<BoaApp> appPageList = bschBaseOpenApi.getPcGrzxPage(authUserWithDept.getId(), schId);
List<BoaApp> appPageList = Collections.emptyList();
if (optional.isPresent()) model = optional.get();
if ((model.getWallpaperUrl() == null || model.getWallpaperUrl().isEmpty()) && schId != null) {
List<String> schList = getSchSettingsWallpaper(schId);
if (schList.size() > 0) model.setWallpaperUrl(schList.get(0));
if (model.getWallpaperUrl() == null || model.getWallpaperUrl().isEmpty()) {
model.setWallpaperUrl(resolveFallbackWallpaper(authUserWithDept, schId));
}
List<Map> mapList = appPageList.stream().map(boaApp -> {
Map map = new HashMap<>();
@@ -224,6 +295,32 @@ public class UserSettingsController {
return model;
}
/**
* 兜底壁纸(用户未设置时):本地学校壁纸第一张 → 超管全局壁纸第一张。
* <p>学校壁纸完全本地管理(wallpaper 表 type=1),不再依赖跨仓 schConfig。</p>
*/
private String resolveFallbackWallpaper(BoaUser authUserWithDept, Integer schId) {
List<Wallpaper> wallList = userSettingsService.findWallList(authUserWithDept.getId());
if (schId != null) {
// 1. 本地学校壁纸第一张(type=1,取最新)
Optional<Wallpaper> schWall = wallList.stream()
.filter(w -> w.getType() != null && w.getType() == 1
&& w.getSchId() != null && w.getSchId().equals(schId))
.max(Comparator.comparing(Wallpaper::getId));
if (schWall.isPresent()) {
return FILE_WEB_PATH + schWall.get().getWallpaperUrl().replace("/upload", "");
}
}
// 2. 超管全局壁纸第一张(type=0,取最新)
Optional<Wallpaper> sysWall = wallList.stream()
.filter(w -> w.getType() != null && w.getType() == 0)
.max(Comparator.comparing(Wallpaper::getId));
if (sysWall.isPresent()) {
return FILE_WEB_PATH + sysWall.get().getWallpaperUrl().replace("/upload", "");
}
return null;
}
@GetMapping(value = "getUserDesktopAppList")
public List<Map> getUserDesktopAppList(BoaUser authUserWithDept) {
Optional<UserCurrentRole> currentRoleOpt = userCurrentRoleService.findById(authUserWithDept.getId());
@@ -465,37 +562,6 @@ public class UserSettingsController {
return u;
}
/**
* 获取学校设置的壁纸集合(对齐旧 UserHome GetSchSettingsWallpaper,fastjson → Jackson)。
*/
private List<String> getSchSettingsWallpaper(Integer schId) {
if (SCH_CONF_URL == null || SCH_CONF_URL.isEmpty()) return Collections.emptyList();
String apiPath = SCH_CONF_URL + "/oapi/getSchSetting?schId=" + schId;
List<String> result = new ArrayList<>();
try {
String resultStr = restTemplate.getForObject(apiPath, String.class);
if (resultStr == null || resultStr.isEmpty()) return result;
JsonNode root = objectMapper.readTree(resultStr);
JsonNode dataNode = root.get("data");
if (root.has("code") && "0".equals(root.get("code").asText()) && dataNode != null && !dataNode.isNull()) {
JsonNode appBgUrlNode = dataNode.get("appBackgroundUrl");
if (appBgUrlNode == null) appBgUrlNode = dataNode.get("appBgUrl");
String appBgUrlStr = appBgUrlNode != null ? appBgUrlNode.asText("") : "";
if (appBgUrlStr.isEmpty()) return result;
String[] imgUrls = appBgUrlStr.split(",");
for (String imgUrl : imgUrls) {
if (!imgUrl.contains(SCH_CONF_URL))
result.add(SCH_CONF_URL + imgUrl);
else
result.add(imgUrl);
}
}
} catch (Exception e) {
// 跨仓调用失败时返回空列表
}
return result;
}
/**
* 将某个人常用应用转化为桌面应用
*/
@@ -0,0 +1,60 @@
package com.yunhai.userhome.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* 桌面应用类别(自 systemsetting.app_category 1:1 迁移)。
*
* <p>roleType:1 校管理员 | 2 教师 | 3 学生(见 {@code enums.RoleType});
* resDate 为资源更新时间戳,前端上传模板后经 UpdateResDate 刷新缓存。</p>
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table(name = "app_category")
public class AppCategory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@NotNull(groups = Update.class)
private Integer id;
private String name;
@Column(name = "api_url", length = 500)
private String apiUrl;
@Column(name = "data_key")
private String dataKey;
/** 接口参数 JSON 数组字符串([{k,v,type}]) */
@Column(name = "params_key", columnDefinition = "TEXT")
private String paramsKey;
@Column(name = "role_type")
private Integer roleType;
@Column(name = "c_id")
private Integer cId;
@Column(name = "c_date")
private Date cDate = new Date();
/** 资源更新时间戳 */
@Column(name = "res_date")
private Date resDate = new Date();
public @interface Update {}
}
@@ -13,8 +13,8 @@ import javax.persistence.Table;
/**
* 学校设置(sch_settings 表,字段名/表名对齐旧 UserHome model.SchSettings)。
* <p>注意:旧版 {@code SchSettingsService.getSchSettings} 并不查本地表,而是跨仓调用
* {@code SCH_CONF_URL/oapi/getSchSetting},返回结构经 fastjson 映射为此实体。本工程保持同一行为。</p>
* <p>本工程学校设置完全本地化:saveSchTheme/saveSchLogo 写入本地表,getSchSettings 直接查本地表,
* 不再依赖旧版跨仓 {@code SCH_CONF_URL/oapi/getSchSetting}。</p>
*/
@AllArgsConstructor
@NoArgsConstructor
@@ -0,0 +1,22 @@
package com.yunhai.userhome.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 桌面应用角色枚举(替代原 GetBaseDict 字典接口,前端本地枚举)。
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum RoleType {
SCHOOL_ADMIN(1, "校管理员"),
TEACHER(2, "教师"),
STUDENT(3, "学生"),
;
private Integer key;
private String value;
}
@@ -0,0 +1,12 @@
package com.yunhai.userhome.repository;
import com.yunhai.userhome.entity.AppCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.List;
public interface AppCategoryRepository extends JpaRepository<AppCategory, Integer>, JpaSpecificationExecutor<AppCategory> {
List<AppCategory> findAllByRoleType(Integer roleType);
}
@@ -0,0 +1,22 @@
package com.yunhai.userhome.service;
import com.yunhai.userhome.entity.AppCategory;
import org.springframework.data.domain.Page;
import java.util.List;
import java.util.Optional;
public interface IAppCategoryService {
void save(AppCategory appCategory);
void deleteById(Integer id);
Optional<AppCategory> findById(Integer id);
List<AppCategory> findByRoleType(Integer roleType);
Page<AppCategory> findPageList(Integer page, Integer count, Integer roleType, String name);
void updateResDate(Integer id);
}
@@ -14,6 +14,12 @@ public interface IUserSettingsService {
List<Wallpaper> findWallList(Integer userId);
/** 管理视角壁纸列表(系统设置页):系统 + 目标学校,不含个人壁纸;isEdit 按调用者权限标记 */
List<Wallpaper> findSchWallpaperManageList(BoaUser user, Integer targetSchId);
/** 管理视角 Logo 列表(系统设置页):系统 + 目标学校 */
List<Logo> findSchLogoManageList(BoaUser user, Integer targetSchId);
void deleteWallById(Integer id, BoaUser user);
void saveWallpaper(Wallpaper wallpaper);
@@ -0,0 +1,82 @@
package com.yunhai.userhome.service.impl;
import com.yunhai.platform.apistd.BizException;
import com.yunhai.platform.utils.exception.ErrorCode;
import com.yunhai.userhome.entity.AppCategory;
import com.yunhai.userhome.repository.AppCategoryRepository;
import com.yunhai.userhome.service.IAppCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
/**
* 应用类别服务(自 systemsetting AppCategoryService 1:1 迁移,GetAppCategorySel/getTimestampByIds 无消费方不迁)。
*/
@Service
public class AppCategoryServiceImpl implements IAppCategoryService {
@Autowired
private AppCategoryRepository appCategoryRepository;
@Override
public void save(AppCategory appCategory) {
appCategoryRepository.save(appCategory);
}
@Override
public void deleteById(Integer id) {
if (!appCategoryRepository.existsById(id)) {
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR);
}
appCategoryRepository.deleteById(id);
}
@Override
public Optional<AppCategory> findById(Integer id) {
return appCategoryRepository.findById(id);
}
@Override
public List<AppCategory> findByRoleType(Integer roleType) {
return appCategoryRepository.findAllByRoleType(roleType);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public Page<AppCategory> findPageList(Integer page, Integer count, Integer roleType, String name) {
Sort sort = Sort.by(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(page - 1, count, sort);
Specification specification = (Specification) (root, criteriaQuery, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (roleType != null) {
predicates.add(criteriaBuilder.equal(root.get("roleType"), roleType));
}
if (name != null && !name.trim().isEmpty()) {
predicates.add(criteriaBuilder.like(root.get("name"), "%" + name + "%"));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
};
return appCategoryRepository.findAll(specification, pageable);
}
@Override
public void updateResDate(Integer id) {
Optional<AppCategory> optionalAppCategory = findById(id);
if (!optionalAppCategory.isPresent()) {
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR);
}
AppCategory model = optionalAppCategory.get();
model.setResDate(new Date());
appCategoryRepository.save(model);
}
}
@@ -1,22 +1,16 @@
package com.yunhai.userhome.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunhai.userhome.entity.SchSettings;
import com.yunhai.userhome.repository.SchSettingsRepository;
import com.yunhai.userhome.service.ISchSettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Optional;
/**
* 学校设置服务:行为对齐旧 UserHome SchSettingsService。
* <p>关键:{@code findBySchId} 不查本地表,而是跨仓调用 {@code SCH_CONF_URL/oapi/getSchSetting},
* 旧版如此(schConfig 服务为 homeImgUrl/isEnableDesktop/isLinkDesktop 等字段的权威来源),
* 因此本地 sch_settings 表变更类能力(saveSchTheme/saveSchLogo)本期仅保留 P1 占位写入本地表。</p>
* 学校设置服务:学校设置完全本地化(sch_settings 表),不再依赖跨仓 schConfig。
* <p>findBySchId 直接查本地表,查不到兜底默认主题;saveSchTheme/saveSchLogo 写本地表即可闭环。</p>
*/
@Service
public class SchSettingsServiceImpl implements ISchSettingsService {
@@ -24,12 +18,6 @@ public class SchSettingsServiceImpl implements ISchSettingsService {
@Autowired
private SchSettingsRepository schSettingsRepository;
@Value("${SCH_CONF_URL:}")
private String schConfUrl;
private final RestTemplate restTemplate = new RestTemplate();
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void save(SchSettings schSettings) {
schSettingsRepository.save(schSettings);
@@ -37,41 +25,16 @@ public class SchSettingsServiceImpl implements ISchSettingsService {
@Override
public Optional<SchSettings> findBySchId(Integer schId) {
SchSettings model = new SchSettings();
try {
if (schConfUrl == null || schConfUrl.isEmpty()) {
return Optional.of(localFallbackBySchId(schId));
}
String apiPath = schConfUrl + "/oapi/getSchSetting?schId=" + schId;
String resultStr = restTemplate.getForObject(apiPath, String.class);
if (resultStr == null || resultStr.isEmpty()) {
return Optional.of(localFallbackBySchId(schId));
}
JsonNode root = objectMapper.readTree(resultStr);
JsonNode dataNode = root.get("data");
if (root.has("code") && "0".equals(root.get("code").asText()) && dataNode != null && !dataNode.isNull()) {
model = objectMapper.convertValue(dataNode, SchSettings.class);
} else {
return Optional.of(localFallbackBySchId(schId));
}
} catch (Exception e) {
return Optional.of(localFallbackBySchId(schId));
}
return Optional.of(model);
}
/** 跨仓不通时先查本地表,再兜底默认主题(对齐旧版异常分支)。 */
private SchSettings localFallbackBySchId(Integer schId) {
Optional<SchSettings> local = schSettingsRepository.findFirstBySchId(schId);
if (local.isPresent()) {
return local.get();
return local;
}
SchSettings def = new SchSettings();
def.setSchId(schId);
def.setThemeName("default-theme");
def.setAppBgUrl("");
def.setLogoUrl("");
return def;
return Optional.of(def);
}
@Override
@@ -73,6 +73,52 @@ public class UserSettingsServiceImpl implements IUserSettingsService {
return resultList;
}
@Override
public List<Wallpaper> findSchWallpaperManageList(BoaUser user, Integer targetSchId) {
UserCurrentRole currentRole = userCurrentRoleService.findById(user.getId())
.orElseThrow(() -> new BizException(ErrorCode.INTERNAL_SERVER_ERROR));
boolean superAdmin = user.getType() == 1;
if (!superAdmin && !OtherUtil.isAdminById(currentRole.getRoleId())) {
throw new BizException(ErrorCode.FORBIDDEN);
}
Integer schId = superAdmin ? (targetSchId == null ? 0 : targetSchId) : currentRole.getSchId();
List<Wallpaper> resultList = new ArrayList<>();
List<Wallpaper> baseList = wallpaperRepository.findBySchIdAndType(0, 0);
List<Wallpaper> schList = wallpaperRepository.findBySchIdAndType(schId, 1);
if (superAdmin) {
baseList.forEach(x -> x.setIsEdit(true));
schList.forEach(x -> x.setIsEdit(true));
} else {
schList.forEach(x -> x.setIsEdit(true));
}
resultList.addAll(baseList);
resultList.addAll(schList);
return resultList;
}
@Override
public List<Logo> findSchLogoManageList(BoaUser user, Integer targetSchId) {
UserCurrentRole currentRole = userCurrentRoleService.findById(user.getId())
.orElseThrow(() -> new BizException(ErrorCode.INTERNAL_SERVER_ERROR));
boolean superAdmin = user.getType() == 1;
if (!superAdmin && !OtherUtil.isAdminById(currentRole.getRoleId())) {
throw new BizException(ErrorCode.FORBIDDEN);
}
Integer schId = superAdmin ? (targetSchId == null ? 0 : targetSchId) : currentRole.getSchId();
List<Logo> resultList = new ArrayList<>();
List<Logo> baseList = logoRepository.findBySchIdAndType(0, 0);
List<Logo> schList = logoRepository.findBySchIdAndType(schId, 1);
if (superAdmin) {
baseList.forEach(x -> x.setIsEdit(true));
schList.forEach(x -> x.setIsEdit(true));
} else {
schList.forEach(x -> x.setIsEdit(true));
}
resultList.addAll(baseList);
resultList.addAll(schList);
return resultList;
}
@Override
@Transactional
public void deleteWallById(Integer id, BoaUser user) {
@@ -6,13 +6,20 @@ import com.yunhai.platform.utils.exception.ErrorCode;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* File工具类(对齐旧 UserHome utils.FileUtil,仅保留 uploadImg 方法——本期迁移实际使用的唯一方法)。
@@ -95,4 +102,156 @@ public class FileUtil {
file.delete();
}
}
/**
* 通用文件上传(MultipartFile 版),保留原始扩展名,返回生成的文件名。
* <p>与 {@link #uploadImg(MultipartFile, String)} 的区别:不做图片扩展名白名单校验,
* 供模板上传(zip/rar)等非图片场景使用,扩展名合法性由调用方自行校验。</p>
*/
public static String uploadFile(MultipartFile file, String pathName) {
String imgName = null;
String path = null;
File floorFile = null;
try {
byte[] fileData = file.getBytes();
String fileName = file.getOriginalFilename();
int index = fileName == null ? -1 : fileName.lastIndexOf(".");
String ext = "";
if (index > 0) {
ext = fileName.substring(index).toLowerCase(Locale.ROOT);
}
for (int i = 0; i < 100; i++) {
imgName = UUID.randomUUID().toString().substring(0, 8) + ext;
path = pathName + imgName;
floorFile = new File(path);
if (floorFile.exists()) continue;
else break;
}
FileOutputStream fos = new FileOutputStream(floorFile);
fos.write(fileData);
fos.close();
} catch (IOException e) {
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "上传失败");
}
return imgName;
}
/**
* 解压缩 ZIP 文件到目标目录(覆盖同名文件,防 zip-slip 路径穿越)。
*
* @param zipFile 压缩包
* @param destDir 目标目录
*/
public static void zipDecompress(File zipFile, File destDir) {
byte[] buffer = new byte[1024];
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
ZipEntry entry = zis.getNextEntry();
while (entry != null) {
File file = new File(destDir, entry.getName());
String destPath = destDir.getCanonicalPath();
String childPath = file.getCanonicalPath();
if (!childPath.startsWith(destPath + File.separator)) {
throw new BizException(ErrorCode.CUSTOM, "压缩包内存在非法路径");
}
if (entry.isDirectory()) {
file.mkdirs();
} else {
File parent = file.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
try (FileOutputStream fos = new FileOutputStream(file)) {
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
}
entry = zis.getNextEntry();
}
} catch (IOException e) {
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "解压失败");
}
}
/**
* 压缩文件/文件夹为 ZIP。
*
* @param sourceFile 被压缩文件/文件夹
* @param zipFile Zip 文件
*/
public static void zipCompress(File sourceFile, File zipFile) {
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
zos.setMethod(ZipOutputStream.DEFLATED);
zos.setLevel(Deflater.BEST_COMPRESSION);
if (sourceFile.exists() && sourceFile.isDirectory() && sourceFile.listFiles() != null) {
Arrays.stream(sourceFile.listFiles()).forEach(file -> addZipFile(file, zos));
} else {
addZipFile(sourceFile, zos);
}
} catch (IOException e) {
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "压缩失败");
}
}
/**
* 向 ZIP 中添加单个文件(zipCompress 辅助)。
*/
private static void addZipFile(File file, ZipOutputStream zos) {
if (!file.exists() || file.isDirectory()) {
throw new RuntimeException("文件不存在或该文件为文件夹,请检查");
}
try (FileInputStream fis = new FileInputStream(file)) {
ZipEntry entry = new ZipEntry(file.getName());
entry.setMethod(ZipEntry.DEFLATED);
entry.setSize(file.length());
entry.setCreationTime(FileTime.from(Instant.now()));
entry.setLastAccessTime(FileTime.from(Instant.now()));
entry.setLastModifiedTime(FileTime.from(Instant.now()));
zos.putNextEntry(entry);
byte[] buf = new byte[1024];
int len;
while ((len = fis.read(buf)) > 0) {
zos.write(buf, 0, len);
}
zos.closeEntry();
} catch (IOException e) {
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "压缩失败");
}
}
/**
* 删除文件夹(含内容)。
*/
public static void delFolder(String folderPath) {
delAllFile(folderPath);
new File(folderPath).delete();
}
/**
* 删除指定文件夹下所有文件(保留目录本身)。
*/
public static boolean delAllFile(String path) {
boolean flag = false;
File file = new File(path);
if (!file.exists() || !file.isDirectory()) {
return flag;
}
String[] tempList = file.list();
if (tempList == null) {
return flag;
}
for (String s : tempList) {
File temp = new File(path, s);
if (temp.isFile()) {
temp.delete();
}
if (temp.isDirectory()) {
delAllFile(temp.getPath());
delFolder(temp.getPath());
flag = true;
}
}
return flag;
}
}
+6 -3
View File
@@ -30,6 +30,11 @@ spring:
hibernate:
jdbc:
time_zone: Asia/Shanghai
servlet:
multipart:
# 模板上传(zip/rar)等大文件支持,对齐 Nacos prod 配置
max-file-size: 50MB
max-request-size: 200MB
redis:
host: localhost
port: 6379
@@ -70,9 +75,7 @@ logging:
# ===== 文件上传与学校配置 =====
# 文件存储根目录(UserSettingsController 壁纸/图标上传使用)
# 本地默认 ./upload/(相对工作目录);生产由 Nacos 覆盖为 /data/upload(容器卷挂载到宿主机)
FILE_PATH: ./upload/
FILE_PATH: D:/bsch/userhome/upload/
# 文件 Web 访问前缀(拼接壁纸完整 URL 用,见 UserSettingsController.getUserWallpaperList)
# 本地 http://localhost:8070/upload/;生产由 Nacos 覆盖为 http://home.yunhaixingtong.com/userhome/upload/
FILE_WEB_PATH: http://localhost:8070/upload/
# 学校配置接口基址(SchSettingsController 使用,prod 由 Nacos 覆盖)
SCH_CONF_URL: ""
-10
View File
@@ -1,10 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" >
<g clip-path="url(#clip0_1309_6469)">
<path d="M23.6935 22.1975H0.29991C0.13436 22.1975 0 22.0631 0 21.8976V21.2977C0 21.1322 0.13436 20.9978 0.29991 20.9978H2.39934V10.2002C2.39934 9.53796 2.93619 9.00052 3.599 9.00052H8.39769V6.60177C8.39769 5.93893 8.93455 5.40209 9.59737 5.40209H14.3961V3.00215C14.3961 2.33993 14.9329 1.80249 15.5957 1.80249H20.3944C21.0566 1.80249 21.5941 2.33993 21.5941 3.00215V20.9978H23.6935C23.8591 20.9978 23.9934 21.1322 23.9934 21.2977V21.8976C23.9934 22.0631 23.8591 22.1975 23.6935 22.1975ZM8.39769 10.2002H3.599V20.9978H8.39769V10.2002ZM14.3961 6.60118H9.59737V20.9978H14.3961V6.60118ZM20.3944 3.00277H15.5957V20.9978H20.3944V3.00277Z" />
</g>
<defs>
<clipPath id="clip0_1309_6469">
<rect width="24" height="24"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 886 B

-10
View File
@@ -1,10 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" >
<g clip-path="url(#clip0_1309_6469)">
<path d="M23.6935 22.1975H0.29991C0.13436 22.1975 0 22.0631 0 21.8976V21.2977C0 21.1322 0.13436 20.9978 0.29991 20.9978H2.39934V10.2002C2.39934 9.53796 2.93619 9.00052 3.599 9.00052H8.39769V6.60177C8.39769 5.93893 8.93455 5.40209 9.59737 5.40209H14.3961V3.00215C14.3961 2.33993 14.9329 1.80249 15.5957 1.80249H20.3944C21.0566 1.80249 21.5941 2.33993 21.5941 3.00215V20.9978H23.6935C23.8591 20.9978 23.9934 21.1322 23.9934 21.2977V21.8976C23.9934 22.0631 23.8591 22.1975 23.6935 22.1975ZM8.39769 10.2002H3.599V20.9978H8.39769V10.2002ZM14.3961 6.60118H9.59737V20.9978H14.3961V6.60118ZM20.3944 3.00277H15.5957V20.9978H20.3944V3.00277Z" />
</g>
<defs>
<clipPath id="clip0_1309_6469">
<rect width="24" height="24"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 886 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="28" height="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg">
<path d="M24.0476 21.4494H16.86V23.3254L18.288 24.8248V25.2H9.7102V24.8248L11.141 23.3268V21.4508H3.9506C3.8009 21.4525 3.65233 21.4246 3.51342 21.3687C3.37451 21.3129 3.24798 21.2302 3.14108 21.1254C3.03418 21.0206 2.94901 20.8957 2.89045 20.7579C2.83189 20.6201 2.80108 20.4721 2.7998 20.3224V5.32561C2.7998 4.70401 3.315 4.20001 3.9506 4.20001H24.0476C24.6846 4.20001 25.1998 4.70401 25.1998 5.32561V20.3252C25.198 20.4747 25.1667 20.6223 25.1078 20.7597C25.0489 20.8971 24.9636 21.0216 24.8566 21.126C24.7496 21.2304 24.6231 21.3127 24.4843 21.3682C24.3454 21.4236 24.1971 21.4513 24.0476 21.4494ZM23.664 5.69941H4.3342V17.1836H23.664V5.70081V5.69941Z" />
</svg>

Before

Width:  |  Height:  |  Size: 752 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="28" height="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg">
<path d="M24.0476 21.4494H16.86V23.3254L18.288 24.8248V25.2H9.7102V24.8248L11.141 23.3268V21.4508H3.9506C3.8009 21.4525 3.65233 21.4246 3.51342 21.3687C3.37451 21.3129 3.24798 21.2302 3.14108 21.1254C3.03418 21.0206 2.94901 20.8957 2.89045 20.7579C2.83189 20.6201 2.80108 20.4721 2.7998 20.3224V5.32561C2.7998 4.70401 3.315 4.20001 3.9506 4.20001H24.0476C24.6846 4.20001 25.1998 4.70401 25.1998 5.32561V20.3252C25.198 20.4747 25.1667 20.6223 25.1078 20.7597C25.0489 20.8971 24.9636 21.0216 24.8566 21.126C24.7496 21.2304 24.6231 21.3127 24.4843 21.3682C24.3454 21.4236 24.1971 21.4513 24.0476 21.4494ZM23.664 5.69941H4.3342V17.1836H23.664V5.70081V5.69941Z" />
</svg>

Before

Width:  |  Height:  |  Size: 752 B

-10
View File
@@ -1,10 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" >
<g clip-path="url(#clip0_1309_6469)">
<path d="M23.6935 22.1975H0.29991C0.13436 22.1975 0 22.0631 0 21.8976V21.2977C0 21.1322 0.13436 20.9978 0.29991 20.9978H2.39934V10.2002C2.39934 9.53796 2.93619 9.00052 3.599 9.00052H8.39769V6.60177C8.39769 5.93893 8.93455 5.40209 9.59737 5.40209H14.3961V3.00215C14.3961 2.33993 14.9329 1.80249 15.5957 1.80249H20.3944C21.0566 1.80249 21.5941 2.33993 21.5941 3.00215V20.9978H23.6935C23.8591 20.9978 23.9934 21.1322 23.9934 21.2977V21.8976C23.9934 22.0631 23.8591 22.1975 23.6935 22.1975ZM8.39769 10.2002H3.599V20.9978H8.39769V10.2002ZM14.3961 6.60118H9.59737V20.9978H14.3961V6.60118ZM20.3944 3.00277H15.5957V20.9978H20.3944V3.00277Z" />
</g>
<defs>
<clipPath id="clip0_1309_6469">
<rect width="24" height="24"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 KiB