diff --git a/backend/pom.xml b/backend/pom.xml index b11abed..b8548a7 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -62,6 +62,11 @@ poi-ooxml 5.2.5 + + com.belerweb + pinyin4j + 2.5.1 + io.jsonwebtoken jjwt-api diff --git a/backend/src/main/java/com/writeoff/module/expert/service/ExpertService.java b/backend/src/main/java/com/writeoff/module/expert/service/ExpertService.java index 909c026..97dd4af 100644 --- a/backend/src/main/java/com/writeoff/module/expert/service/ExpertService.java +++ b/backend/src/main/java/com/writeoff/module/expert/service/ExpertService.java @@ -12,6 +12,11 @@ import com.writeoff.module.expert.model.ExpertBankCardInfo; import com.writeoff.module.expert.model.ExpertInfo; import com.writeoff.module.system.service.DataPermissionService; import com.writeoff.security.AuthContext; +import net.sourceforge.pinyin4j.PinyinHelper; +import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType; +import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat; +import net.sourceforge.pinyin4j.format.HanyuPinyinToneType; +import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; @@ -114,12 +119,31 @@ public class ExpertService { StringBuilder whereClause = new StringBuilder( "WHERE e.tenant_id=" + PLATFORM_TENANT_ID + " AND e.is_deleted=0" ); - List countArgs = new ArrayList<>(); if (keyword != null && !keyword.trim().isEmpty()) { String kw = keyword.trim().replace("'", "''"); whereClause.append(" AND (e.expert_name LIKE '%").append(kw).append("%' OR e.id_no LIKE '%").append(kw).append("%')"); } + com.writeoff.module.system.service.DataPermissionService.DataScope scope = dataPermissionService.resolveCurrentUserScope(); + if (!scope.isExpertAll()) { + boolean hasIdScope = !scope.getExpertIds().isEmpty(); + boolean hasOwnerScope = scope.isExpertOwnerOnly(); + if (!hasIdScope && !hasOwnerScope) { + return new PageResult(new ArrayList<>(), 0, safePage, safeSize); + } + whereClause.append(" AND (1=0"); + if (hasIdScope) { + whereClause.append(" OR e.id IN (").append(scope.getExpertIds().stream().map(String::valueOf).collect(Collectors.joining(","))).append(")"); + } + if (hasOwnerScope) { + Long userId = AuthContext.userId(); + if (userId != null) { + whereClause.append(" OR e.created_by=").append(userId); + } + } + whereClause.append(")"); + } + Integer total = jdbcTemplate.queryForObject( "SELECT COUNT(1) FROM expert e " + whereClause, Integer.class @@ -138,7 +162,6 @@ public class ExpertService { sql.append(whereClause); sql.append(" ORDER BY e.id DESC LIMIT ").append(safeSize).append(" OFFSET ").append(offset); List list = jdbcTemplate.query(sql.toString(), EXPERT_ROW_MAPPER); - list = filterByExpertScope(list); List maskedList = new java.util.ArrayList(list.size()); for (ExpertInfo item : list) { maskedList.add(maskSensitiveFields(item)); @@ -195,11 +218,13 @@ public class ExpertService { @Transactional(rollbackFor = Exception.class) public ExpertInfo create(CreateExpertRequest request) { + String idNo = request.getIdNo() == null ? "" : request.getIdNo().trim().toUpperCase(); + request.setIdNo(idNo); Integer count = jdbcTemplate.queryForObject( "SELECT COUNT(1) FROM expert WHERE tenant_id=? AND id_no=? AND is_deleted=0", Integer.class, PLATFORM_TENANT_ID, - request.getIdNo() + idNo ); if (count != null && count > 0) { throw new BusinessException(10001, "身份证号已存在"); @@ -617,11 +642,76 @@ public class ExpertService { name ); if (byName.isEmpty()) { - throw new BusinessException(10001, label + "不在平台字典内,请先在平台字典中维护"); + String pinyinCode = getPinyin(name); + if (pinyinCode.length() > 40) { + pinyinCode = pinyinCode.substring(0, 40); + } + Integer count = jdbcTemplate.queryForObject( + "SELECT COUNT(1) FROM platform_dictionary_item WHERE dict_type=? AND dict_code=?", + Integer.class, + dictType, + pinyinCode + ); + if (count != null && count > 0) { + int i = 1; + while(true) { + String newCode = pinyinCode + "_" + i; + if (newCode.length() > 50) { + newCode = newCode.substring(newCode.length() - 50); + } + Integer c = jdbcTemplate.queryForObject( + "SELECT COUNT(1) FROM platform_dictionary_item WHERE dict_type=? AND dict_code=?", + Integer.class, dictType, newCode + ); + if (c == null || c == 0) { + pinyinCode = newCode; + break; + } + i++; + } + } + Integer maxSort = jdbcTemplate.queryForObject( + "SELECT MAX(sort_no) FROM platform_dictionary_item WHERE dict_type=?", + Integer.class, + dictType + ); + int nextSort = (maxSort == null ? 0 : maxSort) + 10; + jdbcTemplate.update( + "INSERT INTO platform_dictionary_item (dict_type, dict_code, dict_name, sort_no, status, remark, created_by, updated_by) " + + "VALUES (?, ?, ?, ?, 'ENABLED', 'Auto-imported', ?, ?)", + dictType, pinyinCode, name, nextSort, safeUserId(), safeUserId() + ); + return new DictionaryItem(pinyinCode, name); } return byName.get(0); } + private String getPinyin(String src) { + if (src == null || src.trim().isEmpty()) { + return ""; + } + StringBuilder result = new StringBuilder(); + HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat(); + format.setCaseType(HanyuPinyinCaseType.UPPERCASE); + format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); + format.setVCharType(HanyuPinyinVCharType.WITH_V); + for (char c : src.trim().toCharArray()) { + if (Character.toString(c).matches("[\\u4E00-\\u9FA5]+")) { + try { + String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(c, format); + if (pinyins != null && pinyins.length > 0) { + result.append(pinyins[0]); + } + } catch (Exception e) { + result.append(c); + } + } else { + result.append(Character.toUpperCase(c)); + } + } + return result.toString(); + } + private void validateImportExpert(CreateExpertRequest request, Set batchIdNos, Set batchPhones) { if (request == null) { throw new BusinessException(10001, "导入行不能为空"); diff --git a/backend/src/main/java/com/writeoff/module/expert/service/PlatformExpertService.java b/backend/src/main/java/com/writeoff/module/expert/service/PlatformExpertService.java index 36e03a5..0dd0820 100644 --- a/backend/src/main/java/com/writeoff/module/expert/service/PlatformExpertService.java +++ b/backend/src/main/java/com/writeoff/module/expert/service/PlatformExpertService.java @@ -11,6 +11,11 @@ import com.writeoff.module.file.service.OssService; import com.writeoff.module.expert.model.ExpertBankCardInfo; import com.writeoff.module.expert.model.ExpertInfo; import com.writeoff.security.AuthContext; +import net.sourceforge.pinyin4j.PinyinHelper; +import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType; +import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat; +import net.sourceforge.pinyin4j.format.HanyuPinyinToneType; +import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; @@ -188,11 +193,13 @@ public class PlatformExpertService { @Transactional(rollbackFor = Exception.class) public ExpertInfo create(CreateExpertRequest request) { + String idNo = request.getIdNo() == null ? "" : request.getIdNo().trim().toUpperCase(); + request.setIdNo(idNo); Integer count = jdbcTemplate.queryForObject( "SELECT COUNT(1) FROM expert WHERE tenant_id=? AND id_no=? AND is_deleted=0", Integer.class, PLATFORM_TENANT_ID, - request.getIdNo() + idNo ); if (count != null && count > 0) { throw new BusinessException(10001, "身份证号已存在"); @@ -614,11 +621,76 @@ public class PlatformExpertService { name ); if (byName.isEmpty()) { - throw new BusinessException(10001, label + "不在平台字典内,请先在平台字典中维护"); + String pinyinCode = getPinyin(name); + if (pinyinCode.length() > 40) { + pinyinCode = pinyinCode.substring(0, 40); + } + Integer count = jdbcTemplate.queryForObject( + "SELECT COUNT(1) FROM platform_dictionary_item WHERE dict_type=? AND dict_code=?", + Integer.class, + dictType, + pinyinCode + ); + if (count != null && count > 0) { + int i = 1; + while(true) { + String newCode = pinyinCode + "_" + i; + if (newCode.length() > 50) { + newCode = newCode.substring(newCode.length() - 50); + } + Integer c = jdbcTemplate.queryForObject( + "SELECT COUNT(1) FROM platform_dictionary_item WHERE dict_type=? AND dict_code=?", + Integer.class, dictType, newCode + ); + if (c == null || c == 0) { + pinyinCode = newCode; + break; + } + i++; + } + } + Integer maxSort = jdbcTemplate.queryForObject( + "SELECT MAX(sort_no) FROM platform_dictionary_item WHERE dict_type=?", + Integer.class, + dictType + ); + int nextSort = (maxSort == null ? 0 : maxSort) + 10; + jdbcTemplate.update( + "INSERT INTO platform_dictionary_item (dict_type, dict_code, dict_name, sort_no, status, remark, created_by, updated_by) " + + "VALUES (?, ?, ?, ?, 'ENABLED', 'Auto-imported', ?, ?)", + dictType, pinyinCode, name, nextSort, safeUserId(), safeUserId() + ); + return new DictionaryItem(pinyinCode, name); } return byName.get(0); } + private String getPinyin(String src) { + if (src == null || src.trim().isEmpty()) { + return ""; + } + StringBuilder result = new StringBuilder(); + HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat(); + format.setCaseType(HanyuPinyinCaseType.UPPERCASE); + format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); + format.setVCharType(HanyuPinyinVCharType.WITH_V); + for (char c : src.trim().toCharArray()) { + if (Character.toString(c).matches("[\\u4E00-\\u9FA5]+")) { + try { + String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(c, format); + if (pinyins != null && pinyins.length > 0) { + result.append(pinyins[0]); + } + } catch (Exception e) { + result.append(c); + } + } else { + result.append(Character.toUpperCase(c)); + } + } + return result.toString(); + } + private DictionaryItem resolveOptionalDictionaryItem(String dictType, String dictCode, String dictName, String label) { boolean hasCode = dictCode != null && !dictCode.trim().isEmpty(); boolean hasName = dictName != null && !dictName.trim().isEmpty(); diff --git a/backend/src/main/java/com/writeoff/module/meeting/controller/MeetingController.java b/backend/src/main/java/com/writeoff/module/meeting/controller/MeetingController.java index e22b411..2191004 100644 --- a/backend/src/main/java/com/writeoff/module/meeting/controller/MeetingController.java +++ b/backend/src/main/java/com/writeoff/module/meeting/controller/MeetingController.java @@ -172,7 +172,7 @@ public class MeetingController { } @PutMapping("/{id}") - @RequirePermission(value = "meeting.create", dataScope = DataScopeType.MEETING, auditAction = "MEETING_UPDATE") + @RequirePermission(value = "meeting.update", dataScope = DataScopeType.MEETING, auditAction = "MEETING_UPDATE") public ApiResponse update(@PathVariable("id") Long id, @RequestBody @Valid CreateMeetingRequest request) { return ApiResponse.success(meetingService.update(id, request)); diff --git a/backend/src/main/java/com/writeoff/module/meeting/service/MeetingMaterialService.java b/backend/src/main/java/com/writeoff/module/meeting/service/MeetingMaterialService.java index 1f7571a..ad37d55 100644 --- a/backend/src/main/java/com/writeoff/module/meeting/service/MeetingMaterialService.java +++ b/backend/src/main/java/com/writeoff/module/meeting/service/MeetingMaterialService.java @@ -2050,6 +2050,8 @@ public class MeetingMaterialService { requireMeetingInvoiceAttachmentList(map.get("attachments")); } } + } catch (BusinessException e) { + throw e; } catch (Exception e) { throw new BusinessException(10001, "资料内容格式错误或缺少必填字段"); } @@ -3170,7 +3172,7 @@ public class MeetingMaterialService { } Collection list = (Collection) value; if (list.isEmpty()) { - throw new BusinessException(10001, "提交失败,缺少必填字段: invoices"); + return; } for (Object item : list) { if (!(item instanceof Map)) { diff --git a/backend/src/main/java/com/writeoff/module/meeting/service/MeetingService.java b/backend/src/main/java/com/writeoff/module/meeting/service/MeetingService.java index ebe6091..dfe2050 100644 --- a/backend/src/main/java/com/writeoff/module/meeting/service/MeetingService.java +++ b/backend/src/main/java/com/writeoff/module/meeting/service/MeetingService.java @@ -230,8 +230,12 @@ public class MeetingService { double cateringRatio = request.getCateringRatio() == null ? project.getCateringFeeRatio() : normalizeRatio(request.getCateringRatio(), "餐费占比"); assertMeetingRatiosWithinProject(project, laborRatio, cateringRatio); long defaultBudgetCent = calculateDefaultMeetingBudgetCent(project); - if (defaultBudgetCent <= 0L) { - throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "默认会议预算必须大于 0"); + long initialBudgetCent = request.getBudgetCent() != null ? request.getBudgetCent() : defaultBudgetCent; + if (!project.isAllowMeetingOverBudget() && initialBudgetCent > defaultBudgetCent) { + throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "当前项目不允许会议预算超出默认分配额度"); + } + if (initialBudgetCent <= 0L) { + throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "会议预算必须大于 0"); } String now = nowIsoSeconds(); Meeting meeting = new Meeting( @@ -243,7 +247,7 @@ public class MeetingService { request.getLocation(), request.getStartTime(), request.getEndTime(), - defaultBudgetCent, + initialBudgetCent, laborRatio, cateringRatio, MeetingStatus.NOT_STARTED, @@ -401,6 +405,15 @@ public class MeetingService { double laborRatio = request.getLaborRatio() == null ? existing.getLaborRatio() : normalizeRatio(request.getLaborRatio(), "劳务占比"); double cateringRatio = request.getCateringRatio() == null ? existing.getCateringRatio() : normalizeRatio(request.getCateringRatio(), "餐费占比"); assertMeetingRatiosWithinProject(project, laborRatio, cateringRatio); + + long newBudgetCent = request.getBudgetCent() != null ? request.getBudgetCent() : existing.getBudgetCent(); + if (!project.isAllowMeetingOverBudget() && newBudgetCent > existing.getBudgetCent()) { + throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "当前项目不允许调高会议预算"); + } + if (newBudgetCent <= 0L) { + throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "会议预算必须大于 0"); + } + Meeting updated = new Meeting( existing.getId(), existing.getProjectId(), @@ -410,7 +423,7 @@ public class MeetingService { request.getLocation(), request.getStartTime(), request.getEndTime(), - request.getBudgetCent(), + newBudgetCent, laborRatio, cateringRatio, existing.getStatus(), diff --git a/backend/src/main/java/com/writeoff/module/meeting/service/MeetingSummaryExportService.java b/backend/src/main/java/com/writeoff/module/meeting/service/MeetingSummaryExportService.java index 8e38c1f..a5c9482 100644 --- a/backend/src/main/java/com/writeoff/module/meeting/service/MeetingSummaryExportService.java +++ b/backend/src/main/java/com/writeoff/module/meeting/service/MeetingSummaryExportService.java @@ -144,8 +144,8 @@ public class MeetingSummaryExportService { projectName, queryTenantName(tenantId) ); - String meetingCategory = stringValue(meeting.get("meeting_category")); - String location = stringValue(meeting.get("location")); + String meetingCategory = resolveDictName("MEETING_CATEGORY", stringValue(meeting.get("meeting_category"))); + String location = resolveDictName("MEETING_LOCATION", stringValue(meeting.get("location"))); String startTime = stringValue(meeting.get("start_time")); String endTime = stringValue(meeting.get("end_time")); String guestCountText = formatNumber(basicInfo.get("guestCount")); @@ -240,6 +240,26 @@ public class MeetingSummaryExportService { } } + private String resolveDictName(String dictType, String dictCode) { + if (dictCode == null || dictCode.trim().isEmpty()) { + return dictCode; + } + try { + List names = jdbcTemplate.queryForList( + "SELECT dict_name FROM platform_dictionary_item WHERE dict_type=? AND dict_code=? AND is_deleted=0 LIMIT 1", + String.class, + dictType, + dictCode + ); + if (!names.isEmpty() && names.get(0) != null) { + return names.get(0); + } + } catch (Exception ex) { + // ignore + } + return dictCode; + } + private List parseIdList(Object value) { if (!(value instanceof List)) { return Collections.emptyList(); diff --git a/backend/src/main/java/com/writeoff/module/system/controller/UserController.java b/backend/src/main/java/com/writeoff/module/system/controller/UserController.java index cfcbcbf..55b262a 100644 --- a/backend/src/main/java/com/writeoff/module/system/controller/UserController.java +++ b/backend/src/main/java/com/writeoff/module/system/controller/UserController.java @@ -93,9 +93,9 @@ public class UserController { @PostMapping("/{id}/reset-password") @RequirePermission(value = "user.password.reset", dataScope = DataScopeType.TENANT, auditAction = "USER_RESET_PASSWORD") - public ApiResponse resetPassword(@PathVariable("id") Long id, @RequestBody @Valid ResetPasswordRequest request) { - systemUserService.resetPassword(id, request); - return ApiResponse.success("OK"); + public ApiResponse resetPassword(@PathVariable("id") Long id) { + systemUserService.resetPassword(id); + return ApiResponse.success("密码已重置"); } @GetMapping("/{id}/role-history") diff --git a/backend/src/main/java/com/writeoff/module/system/service/SystemUserService.java b/backend/src/main/java/com/writeoff/module/system/service/SystemUserService.java index 4d70a84..1c0005c 100644 --- a/backend/src/main/java/com/writeoff/module/system/service/SystemUserService.java +++ b/backend/src/main/java/com/writeoff/module/system/service/SystemUserService.java @@ -22,6 +22,7 @@ import com.writeoff.module.system.model.UserRoleHistory; import com.writeoff.security.AuthContext; import com.writeoff.security.PasswordCodecService; import com.writeoff.security.PasswordPolicyService; +import com.writeoff.security.PasswordSetupService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcTemplate; @@ -55,7 +56,9 @@ public class SystemUserService { private final PasswordPolicyService passwordPolicyService; private final PasswordCodecService passwordCodecService; private final TransactionTemplate transactionTemplate; + private final PasswordSetupService passwordSetupService; private final ObjectMapper objectMapper = new ObjectMapper(); + private final String frontendBaseUrl; private static final RowMapper USER_ROW_MAPPER = (rs, n) -> new SystemUser( rs.getLong("id"), @@ -97,13 +100,17 @@ public class SystemUserService { NotificationDispatchService notificationDispatchService, PasswordPolicyService passwordPolicyService, PasswordCodecService passwordCodecService, - PlatformTransactionManager transactionManager) { + PlatformTransactionManager transactionManager, + PasswordSetupService passwordSetupService, + @org.springframework.beans.factory.annotation.Value("${app.frontend-base-url:http://localhost:5173}") String frontendBaseUrl) { this.jdbcTemplate = jdbcTemplate; this.dataPermissionService = dataPermissionService; this.notificationDispatchService = notificationDispatchService; this.passwordPolicyService = passwordPolicyService; this.passwordCodecService = passwordCodecService; this.transactionTemplate = new TransactionTemplate(transactionManager); + this.passwordSetupService = passwordSetupService; + this.frontendBaseUrl = frontendBaseUrl; } public PageResult listUsers(int pageNo, int pageSize, Boolean includeDeleted) { @@ -179,10 +186,8 @@ public class SystemUserService { final String phone = request.getPhone() == null ? "" : request.getPhone().trim(); final String email = request.getEmail() == null ? "" : request.getEmail().trim(); final String rawPassword = request.getPassword() == null ? "" : request.getPassword().trim(); - if (rawPassword.isEmpty()) { - throw new BusinessException(10001, "\u5bc6\u7801\u4e0d\u80fd\u4e3a\u7a7a"); - } - passwordPolicyService.validate(rawPassword); + final String finalPassword = rawPassword.isEmpty() ? ("Tmp@" + java.util.UUID.randomUUID().toString().replace("-", "").substring(0, 8) + "aA1") : rawPassword; + passwordPolicyService.validate(finalPassword); final String validFrom = request.getValidFrom() == null || request.getValidFrom().trim().isEmpty() ? LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : normalizeDateTimeString(request.getValidFrom()); @@ -191,7 +196,7 @@ public class SystemUserService { : normalizeDateTimeString(request.getValidTo()); return transactionTemplate.execute(status -> { assertPhoneAvailable(phone, null); - String passwordHash = passwordCodecService.encode(rawPassword); + String passwordHash = passwordCodecService.encode(finalPassword); String tenantSwitchAccountKey = resolveTenantSwitchAccountKeyByPhone(phone, null); KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(connection -> { @@ -217,7 +222,8 @@ public class SystemUserService { }, keyHolder); Long id = keyHolder.getKey() == null ? null : keyHolder.getKey().longValue(); autoAssignExecutorRoleWhenCreatorIsProjectExecutor(id); - sendUserCreatedMail(id, userName, phone, email, validFrom, validTo); + String setupLink = passwordSetupService.issueUserSetupLink(tenantId(), id, safeUserId()); + sendUserCreatedMail(id, userName, phone, email, finalPassword, validFrom, validTo, setupLink); return new SystemUser(id, userName, phone, email, "ENABLED", validFrom, validTo, "", ""); }); } @@ -248,8 +254,8 @@ public class SystemUserService { ps.setTimestamp(idx++, validTo == null ? Timestamp.valueOf(LocalDateTime.of(2099, 12, 31, 23, 59, 59)) : validTo); ps.setLong(idx++, operator); if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) { - passwordPolicyService.validate(request.getPassword()); - ps.setString(idx++, passwordCodecService.encode(request.getPassword())); + passwordPolicyService.validate(request.getPassword().trim()); + ps.setString(idx++, passwordCodecService.encode(request.getPassword().trim())); } ps.setLong(idx++, tenantId()); ps.setLong(idx, userId); @@ -413,14 +419,54 @@ public class SystemUserService { ); } - public void resetPassword(Long userId, ResetPasswordRequest request) { + public void resetPassword(Long userId) { assertUserExists(userId); - passwordPolicyService.validate(request.getNewPassword()); - jdbcTemplate.update( - "UPDATE sys_user SET password_hash=?, updated_at=CURRENT_TIMESTAMP WHERE id=?", - passwordCodecService.encode(request.getNewPassword()), + + SystemUser user = jdbcTemplate.queryForObject( + "SELECT id, user_name, phone, email, status, valid_from, valid_to FROM sys_user WHERE tenant_id=? AND id=? AND is_deleted=0 LIMIT 1", + (rs, rowNum) -> new SystemUser( + rs.getLong("id"), + rs.getString("user_name"), + rs.getString("phone"), + rs.getString("email"), + rs.getString("status"), + rs.getString("valid_from"), + rs.getString("valid_to") + ), + tenantId(), userId ); + + String setupLink = passwordSetupService.issueUserSetupLink(tenantId(), userId, safeUserId()); + + List> tenants = jdbcTemplate.queryForList( + "SELECT tenant_code, tenant_name FROM tenant WHERE id=? LIMIT 1", + tenantId() + ); + String tenantCode = tenants.isEmpty() ? "" : String.valueOf(tenants.get(0).get("tenant_code")); + String tenantName = tenants.isEmpty() ? "" : String.valueOf(tenants.get(0).get("tenant_name")); + + Map variables = new LinkedHashMap(); + variables.put("userId", userId); + variables.put("targetUserId", userId); + variables.put("userName", user.getUserName()); + variables.put("phone", user.getPhone()); + variables.put("email", user.getEmail()); + variables.put("tenantCode", tenantCode); + variables.put("tenantName", tenantName); + variables.put("setupLink", setupLink); + + DispatchNotificationRequest dispatchRequest = new DispatchNotificationRequest(); + dispatchRequest.setIdempotencyKey("user-password-reset-" + tenantId() + "-" + userId + "-" + System.currentTimeMillis()); + dispatchRequest.setEventCode("USER_PASSWORD_RESET"); + dispatchRequest.setBizType("USER"); + dispatchRequest.setBizId("user-" + userId); + try { + dispatchRequest.setVariablesJson(objectMapper.writeValueAsString(variables)); + } catch (Exception e) { + log.error("Failed to write variables json", e); + } + notificationDispatchService.dispatch(dispatchRequest); } public void changeMyPassword(Long userId, String oldPassword, String newPassword) { @@ -707,8 +753,8 @@ public class SystemUserService { if (item.getUserName() == null || item.getUserName().trim().isEmpty()) { throw new BusinessException(10001, "用户名不能为空"); } - if (item.getPassword() == null || item.getPassword().trim().isEmpty()) { - throw new BusinessException(10001, "密码不能为空"); + if (item.getPassword() != null && !item.getPassword().trim().isEmpty()) { + passwordPolicyService.validate(item.getPassword().trim()); } ImportValidationUtils.validatePhone(item.getPhone()); ImportValidationUtils.validateRequiredEmail(item.getEmail()); @@ -717,7 +763,6 @@ public class SystemUserService { if (!batchPhones.add(phone)) { throw new BusinessException(10001, "批次内手机号重复"); } - passwordPolicyService.validate(item.getPassword().trim()); String roleCode = ImportValidationUtils.trim(item.getRoleCode()); if (roleCode.isEmpty()) { return null; @@ -750,7 +795,7 @@ public class SystemUserService { : ex.getMessage(); } - private void sendUserCreatedMail(Long userId, String userName, String phone, String email, String validFrom, String validTo) { + private void sendUserCreatedMail(Long userId, String userName, String phone, String email, String rawPassword, String validFrom, String validTo, String setupLink) { if (userId == null || userId <= 0) { return; } @@ -761,18 +806,26 @@ public class SystemUserService { ); String tenantCode = tenants.isEmpty() ? "" : String.valueOf(tenants.get(0).get("tenant_code")); String tenantName = tenants.isEmpty() ? "" : String.valueOf(tenants.get(0).get("tenant_name")); - String loginPath = "/" + tenantCode + "/login"; + String baseUrl = frontendBaseUrl == null ? "" : frontendBaseUrl; + while (baseUrl.endsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + } + String loginPath = baseUrl.isEmpty() ? "/" + tenantCode + "/login" : baseUrl + "/" + tenantCode + "/login"; + String displayValidTo = (validTo != null && validTo.contains("2099-12-31")) ? "长期有效" : validTo; + Map variables = new LinkedHashMap(); variables.put("userId", userId); variables.put("targetUserId", userId); variables.put("userName", userName); variables.put("phone", phone); variables.put("email", email); + variables.put("password", rawPassword); variables.put("validFrom", validFrom); - variables.put("validTo", validTo); + variables.put("validTo", displayValidTo); variables.put("tenantCode", tenantCode); variables.put("tenantName", tenantName); - variables.put("loginPath", loginPath); + variables.put("loginPath", setupLink != null ? setupLink : loginPath); + variables.put("setupLink", setupLink != null ? setupLink : ""); DispatchNotificationRequest request = new DispatchNotificationRequest(); request.setIdempotencyKey("user-created-" + tenantId() + "-" + userId); request.setEventCode("USER_CREATED"); diff --git a/backend/src/main/java/com/writeoff/module/system/service/TenantService.java b/backend/src/main/java/com/writeoff/module/system/service/TenantService.java index 425cfee..8216923 100644 --- a/backend/src/main/java/com/writeoff/module/system/service/TenantService.java +++ b/backend/src/main/java/com/writeoff/module/system/service/TenantService.java @@ -270,7 +270,8 @@ public class TenantService { ); String setupLink = passwordSetupService.issueTenantAdminSetupLink(tenantId, uid, safeUserId()); - sendTenantAdminMail(tenantId, request, action, setupLink); + // 移除了发送邮件功能 + // sendTenantAdminMail(tenantId, request, action, setupLink); java.util.Map data = new java.util.LinkedHashMap(); data.put("tenantId", tenantId); @@ -278,6 +279,7 @@ public class TenantService { data.put("roleId", roleId); data.put("roleCode", roleCode); data.put("action", action); + data.put("setupLink", setupLink); return data; } diff --git a/backend/src/main/java/com/writeoff/module/template/controller/TemplateController.java b/backend/src/main/java/com/writeoff/module/template/controller/TemplateController.java index b7ce9c7..9870ead 100644 --- a/backend/src/main/java/com/writeoff/module/template/controller/TemplateController.java +++ b/backend/src/main/java/com/writeoff/module/template/controller/TemplateController.java @@ -39,11 +39,23 @@ public class TemplateController { @RequestParam(value = "status", required = false) String status, @RequestParam(value = "scopeType", required = false) String scopeType, @RequestParam(value = "bizScene", required = false) String bizScene, - @RequestParam(value = "watermarkEnabled", required = false) Boolean watermarkEnabled, @RequestParam(value = "effectiveStatus", required = false) String effectiveStatus, @RequestParam(value = "pageNo", defaultValue = "1") int pageNo, @RequestParam(value = "pageSize", defaultValue = "20") int pageSize) { - return ApiResponse.success(templateService.list(templateName, templateType, status, scopeType, bizScene, watermarkEnabled, effectiveStatus, pageNo, pageSize)); + return ApiResponse.success(templateService.list(templateName, templateType, status, scopeType, bizScene, effectiveStatus, pageNo, pageSize)); + } + + @GetMapping("/view-list") + @RequirePermission(value = "template.read", dataScope = DataScopeType.TENANT, auditAction = "TEMPLATE_VIEW_LIST") + public ApiResponse> viewList( + @RequestParam(value = "templateName", required = false) String templateName, + @RequestParam(value = "templateType", required = false) String templateType, + @RequestParam(value = "scopeType", required = false) String scopeType, + @RequestParam(value = "bizScene", required = false) String bizScene, + @RequestParam(value = "effectiveStatus", required = false) String effectiveStatus, + @RequestParam(value = "pageNo", defaultValue = "1") int pageNo, + @RequestParam(value = "pageSize", defaultValue = "20") int pageSize) { + return ApiResponse.success(templateService.listView(templateName, templateType, scopeType, bizScene, effectiveStatus, pageNo, pageSize)); } @GetMapping("/published-options") @@ -98,6 +110,12 @@ public class TemplateController { return ApiResponse.success(templateService.create(request)); } + @PostMapping("/{id}/update") + @RequirePermission(value = "template.update", dataScope = DataScopeType.TENANT, auditAction = "TEMPLATE_UPDATE") + public ApiResponse update(@PathVariable("id") Long id, @RequestBody @Valid com.writeoff.module.template.dto.UpdateTemplateRequest request) { + return ApiResponse.success(templateService.update(id, request)); + } + @PostMapping("/upload-sign") @RequirePermission(value = "template.create", dataScope = DataScopeType.TENANT, auditAction = "TEMPLATE_UPLOAD_SIGN") public ApiResponse> uploadSign(@RequestBody @Valid TemplateUploadSignRequest request) { @@ -105,7 +123,7 @@ public class TemplateController { } @GetMapping("/{id}/versions") - @RequirePermission(value = "template.read", dataScope = DataScopeType.TENANT, auditAction = "TEMPLATE_VERSIONS") + @RequirePermission(value = "template.detail.read", dataScope = DataScopeType.TENANT, auditAction = "TEMPLATE_VERSIONS") public ApiResponse> versions(@PathVariable("id") Long id) { return ApiResponse.success(templateService.versions(id)); } @@ -148,16 +166,10 @@ public class TemplateController { return ApiResponse.success(templateService.download(id, request.getRemoteAddr(), request.getHeader("User-Agent"))); } - @GetMapping("/{id}/download-watermark") - @RequirePermission(value = "template.download", dataScope = DataScopeType.TENANT, auditAction = "TEMPLATE_DOWNLOAD_WATERMARK") - public ApiResponse> downloadWatermark(@PathVariable("id") Long id, - @RequestParam(value = "watermarkText", required = false) String watermarkText, - HttpServletRequest request) { - return ApiResponse.success(templateService.downloadWatermark(id, watermarkText, request.getRemoteAddr(), request.getHeader("User-Agent"))); - } + @GetMapping("/{id}/versions/diff") - @RequirePermission(value = "template.read", dataScope = DataScopeType.TENANT, auditAction = "TEMPLATE_VERSION_DIFF") + @RequirePermission(value = "template.detail.read", dataScope = DataScopeType.TENANT, auditAction = "TEMPLATE_VERSION_DIFF") public ApiResponse> versionDiff(@PathVariable("id") Long id, @RequestParam(value = "leftVersionNo", required = false) Integer leftVersionNo, @RequestParam(value = "rightVersionNo", required = false) Integer rightVersionNo) { @@ -172,7 +184,6 @@ public class TemplateController { @RequestParam(value = "userId", required = false) Long userId, @RequestParam(value = "userKeyword", required = false) String userKeyword, @RequestParam(value = "versionNo", required = false) Integer versionNo, - @RequestParam(value = "downloadType", required = false) String downloadType, @RequestParam(value = "ip", required = false) String ip, @RequestParam(value = "downloadedFrom", required = false) String downloadedFrom, @RequestParam(value = "downloadedTo", required = false) String downloadedTo, @@ -184,7 +195,6 @@ public class TemplateController { userId, userKeyword, versionNo, - downloadType, ip, downloadedFrom, downloadedTo, diff --git a/backend/src/main/java/com/writeoff/module/template/dto/CreateTemplateRequest.java b/backend/src/main/java/com/writeoff/module/template/dto/CreateTemplateRequest.java index c4730ff..9616326 100644 --- a/backend/src/main/java/com/writeoff/module/template/dto/CreateTemplateRequest.java +++ b/backend/src/main/java/com/writeoff/module/template/dto/CreateTemplateRequest.java @@ -18,7 +18,6 @@ public class CreateTemplateRequest { private String changeLog; private String effectiveFrom; private String effectiveTo; - private Boolean watermarkEnabled; private Integer downloadRateLimitPerHour; public String getTemplateName() { @@ -109,13 +108,6 @@ public class CreateTemplateRequest { this.effectiveTo = effectiveTo; } - public Boolean getWatermarkEnabled() { - return watermarkEnabled; - } - - public void setWatermarkEnabled(Boolean watermarkEnabled) { - this.watermarkEnabled = watermarkEnabled; - } public Integer getDownloadRateLimitPerHour() { return downloadRateLimitPerHour; diff --git a/backend/src/main/java/com/writeoff/module/template/dto/UpdateTemplateRequest.java b/backend/src/main/java/com/writeoff/module/template/dto/UpdateTemplateRequest.java new file mode 100644 index 0000000..783d9fa --- /dev/null +++ b/backend/src/main/java/com/writeoff/module/template/dto/UpdateTemplateRequest.java @@ -0,0 +1,100 @@ +package com.writeoff.module.template.dto; + +import javax.validation.constraints.NotBlank; + +public class UpdateTemplateRequest { + @NotBlank(message = "模板名称不能为空") + private String templateName; + @NotBlank(message = "模板类型不能为空") + private String templateType; + @NotBlank(message = "适用范围不能为空") + private String scopeType; + private Long projectId; + private Long meetingId; + private Long scopeId; + private String bizScene; + private String effectiveFrom; + private String effectiveTo; + private Integer downloadRateLimitPerHour; + + public String getTemplateName() { + return templateName; + } + + public void setTemplateName(String templateName) { + this.templateName = templateName; + } + + public String getTemplateType() { + return templateType; + } + + public void setTemplateType(String templateType) { + this.templateType = templateType; + } + + public String getScopeType() { + return scopeType; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public Long getProjectId() { + return projectId; + } + + public void setProjectId(Long projectId) { + this.projectId = projectId; + } + + public Long getMeetingId() { + return meetingId; + } + + public void setMeetingId(Long meetingId) { + this.meetingId = meetingId; + } + + public Long getScopeId() { + return scopeId; + } + + public void setScopeId(Long scopeId) { + this.scopeId = scopeId; + } + + public String getBizScene() { + return bizScene; + } + + public void setBizScene(String bizScene) { + this.bizScene = bizScene; + } + + public String getEffectiveFrom() { + return effectiveFrom; + } + + public void setEffectiveFrom(String effectiveFrom) { + this.effectiveFrom = effectiveFrom; + } + + public String getEffectiveTo() { + return effectiveTo; + } + + public void setEffectiveTo(String effectiveTo) { + this.effectiveTo = effectiveTo; + } + + + public Integer getDownloadRateLimitPerHour() { + return downloadRateLimitPerHour; + } + + public void setDownloadRateLimitPerHour(Integer downloadRateLimitPerHour) { + this.downloadRateLimitPerHour = downloadRateLimitPerHour; + } +} diff --git a/backend/src/main/java/com/writeoff/module/template/model/TemplateDownloadLogInfo.java b/backend/src/main/java/com/writeoff/module/template/model/TemplateDownloadLogInfo.java index 3f663fe..e473aba 100644 --- a/backend/src/main/java/com/writeoff/module/template/model/TemplateDownloadLogInfo.java +++ b/backend/src/main/java/com/writeoff/module/template/model/TemplateDownloadLogInfo.java @@ -10,7 +10,6 @@ public class TemplateDownloadLogInfo { private String userPhone; private String objectKey; private String downloadType; - private String watermarkText; private Long projectId; private Long meetingId; private String ip; @@ -18,7 +17,7 @@ public class TemplateDownloadLogInfo { private String downloadedAt; public TemplateDownloadLogInfo(Long id, Long templateId, String templateName, Integer versionNo, Long userId, String userName, String userPhone, - String objectKey, String downloadType, String watermarkText, Long projectId, Long meetingId, + String objectKey, String downloadType, Long projectId, Long meetingId, String ip, String userAgent, String downloadedAt) { this.id = id; this.templateId = templateId; @@ -29,7 +28,6 @@ public class TemplateDownloadLogInfo { this.userPhone = userPhone; this.objectKey = objectKey; this.downloadType = downloadType; - this.watermarkText = watermarkText; this.projectId = projectId; this.meetingId = meetingId; this.ip = ip; @@ -73,9 +71,7 @@ public class TemplateDownloadLogInfo { return downloadType; } - public String getWatermarkText() { - return watermarkText; - } + public Long getProjectId() { return projectId; diff --git a/backend/src/main/java/com/writeoff/module/template/model/TemplateInfo.java b/backend/src/main/java/com/writeoff/module/template/model/TemplateInfo.java index 15d3c29..1d0ea03 100644 --- a/backend/src/main/java/com/writeoff/module/template/model/TemplateInfo.java +++ b/backend/src/main/java/com/writeoff/module/template/model/TemplateInfo.java @@ -14,13 +14,12 @@ public class TemplateInfo { private String currentObjectKey; private String effectiveFrom; private String effectiveTo; - private Boolean watermarkEnabled; private Integer downloadRateLimitPerHour; private String createdAt; private String updatedAt; public TemplateInfo(Long id, String templateName, String templateType, String scopeType, Long projectId, Long meetingId, Long scopeId, String bizScene, - String status, Integer currentVersionNo, String currentObjectKey, String effectiveFrom, String effectiveTo, Boolean watermarkEnabled, Integer downloadRateLimitPerHour, + String status, Integer currentVersionNo, String currentObjectKey, String effectiveFrom, String effectiveTo, Integer downloadRateLimitPerHour, String createdAt, String updatedAt) { this.id = id; this.templateName = templateName; @@ -35,7 +34,6 @@ public class TemplateInfo { this.currentObjectKey = currentObjectKey; this.effectiveFrom = effectiveFrom; this.effectiveTo = effectiveTo; - this.watermarkEnabled = watermarkEnabled; this.downloadRateLimitPerHour = downloadRateLimitPerHour; this.createdAt = createdAt; this.updatedAt = updatedAt; @@ -93,9 +91,6 @@ public class TemplateInfo { return effectiveTo; } - public Boolean getWatermarkEnabled() { - return watermarkEnabled; - } public Integer getDownloadRateLimitPerHour() { return downloadRateLimitPerHour; diff --git a/backend/src/main/java/com/writeoff/module/template/service/TemplateService.java b/backend/src/main/java/com/writeoff/module/template/service/TemplateService.java index 305a92e..e98d2a3 100644 --- a/backend/src/main/java/com/writeoff/module/template/service/TemplateService.java +++ b/backend/src/main/java/com/writeoff/module/template/service/TemplateService.java @@ -59,7 +59,6 @@ public class TemplateService { rs.getString("current_object_key"), rs.getString("effective_from"), rs.getString("effective_to"), - rs.getInt("watermark_enabled") == 1, rs.getInt("download_rate_limit_per_hour"), rs.getString("created_at"), rs.getString("updated_at") @@ -86,7 +85,6 @@ public class TemplateService { rs.getString("user_phone"), rs.getString("object_key"), rs.getString("download_type"), - rs.getString("watermark_text"), rs.getObject("project_id") == null ? null : rs.getLong("project_id"), rs.getObject("meeting_id") == null ? null : rs.getLong("meeting_id"), rs.getString("ip"), @@ -114,7 +112,6 @@ public class TemplateService { String status, String scopeType, String bizScene, - Boolean watermarkEnabled, String effectiveStatus, int pageNo, int pageSize) { @@ -150,10 +147,7 @@ public class TemplateService { whereSql.append(" AND t.biz_scene=?"); whereArgs.add(normalizedBizScene); } - if (watermarkEnabled != null) { - whereSql.append(" AND t.watermark_enabled=?"); - whereArgs.add(Boolean.TRUE.equals(watermarkEnabled) ? 1 : 0); - } + appendEffectiveStatusFilter(whereSql, normalizedEffectiveStatus); Integer total = jdbcTemplate.queryForObject( @@ -176,6 +170,16 @@ public class TemplateService { return new PageResult<>(list, totalCount, safePage, safeSize); } + public PageResult listView(String templateName, + String templateType, + String scopeType, + String bizScene, + String effectiveStatus, + int pageNo, + int pageSize) { + return list(templateName, templateType, "PUBLISHED", scopeType, bizScene, effectiveStatus, pageNo, pageSize); + } + public List listPublishedOptions(String bizScene) { String normalizedBizScene = normalizeOptionalBizScene(bizScene); StringBuilder sql = new StringBuilder(templateSelectSql()) @@ -248,8 +252,8 @@ public class TemplateService { KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement( - "INSERT INTO template (tenant_id, template_name, template_type, scope_type, project_id, meeting_id, scope_id, biz_scene, status, current_version_no, effective_from, effective_to, watermark_enabled, download_rate_limit_per_hour, created_by, updated_by) " + - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'DRAFT', 1, STR_TO_DATE(?, '%Y-%m-%d %H:%i:%s'), STR_TO_DATE(?, '%Y-%m-%d %H:%i:%s'), ?, ?, ?, ?)", + "INSERT INTO template (tenant_id, template_name, template_type, scope_type, project_id, meeting_id, scope_id, biz_scene, status, current_version_no, effective_from, effective_to, download_rate_limit_per_hour, created_by, updated_by) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'DRAFT', 1, STR_TO_DATE(?, '%Y-%m-%d %H:%i:%s'), STR_TO_DATE(?, '%Y-%m-%d %H:%i:%s'), ?, ?, ?)", Statement.RETURN_GENERATED_KEYS ); ps.setLong(1, tenantId()); @@ -262,10 +266,9 @@ public class TemplateService { ps.setString(8, normalizeBizScene(request.getBizScene())); ps.setString(9, effectiveFrom); ps.setString(10, effectiveTo); - ps.setInt(11, Boolean.TRUE.equals(request.getWatermarkEnabled()) ? 1 : 0); - ps.setInt(12, downloadRateLimitPerHour); + ps.setInt(11, downloadRateLimitPerHour); + ps.setLong(12, userId); ps.setLong(13, userId); - ps.setLong(14, userId); return ps; }, keyHolder); Long templateId = keyHolder.getKey() == null ? null : keyHolder.getKey().longValue(); @@ -282,6 +285,36 @@ public class TemplateService { return findById(validTemplateId); } + @Transactional(rollbackFor = Exception.class) + public TemplateInfo update(Long templateId, com.writeoff.module.template.dto.UpdateTemplateRequest request) { + TemplateInfo template = findById(templateId); + assertTemplateEditable(template); + String scopeType = normalizeScope(request.getScopeType()); + String templateType = normalizeTemplateType(request.getTemplateType()); + String effectiveFrom = normalizeOptionalDateTime(request.getEffectiveFrom(), "effectiveFrom"); + String effectiveTo = normalizeOptionalDateTime(request.getEffectiveTo(), "effectiveTo"); + Integer downloadRateLimitPerHour = normalizeDownloadRateLimit(request.getDownloadRateLimitPerHour()); + assertEffectiveRangeValid(effectiveFrom, effectiveTo); + assertTypeOptionEnabled(templateType); + jdbcTemplate.update( + "UPDATE template SET template_name=?, template_type=?, scope_type=?, project_id=?, meeting_id=?, scope_id=?, biz_scene=?, effective_from=STR_TO_DATE(?, '%Y-%m-%d %H:%i:%s'), effective_to=STR_TO_DATE(?, '%Y-%m-%d %H:%i:%s'), download_rate_limit_per_hour=?, updated_by=?, updated_at=CURRENT_TIMESTAMP WHERE tenant_id=? AND id=?", + request.getTemplateName(), + templateType, + scopeType, + request.getProjectId(), + request.getMeetingId(), + request.getScopeId(), + normalizeBizScene(request.getBizScene()), + effectiveFrom, + effectiveTo, + downloadRateLimitPerHour, + safeUserId(), + tenantId(), + templateId + ); + return findById(templateId); + } + public List versions(Long templateId) { assertTemplateExists(templateId); return jdbcTemplate.query( @@ -451,15 +484,14 @@ public class TemplateService { assertTemplateEffectiveNow(template, "下载"); assertDownloadRateLimit(template, userId); jdbcTemplate.update( - "INSERT INTO template_download_log (tenant_id, template_id, version_no, user_id, object_key, download_type, watermark_text, project_id, meeting_id, ip, user_agent) " + - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "INSERT INTO template_download_log (tenant_id, template_id, version_no, user_id, object_key, download_type, project_id, meeting_id, ip, user_agent) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", tenantId(), template.getId(), template.getCurrentVersionNo(), userId, template.getCurrentObjectKey(), "NORMAL", - null, template.getProjectId(), template.getMeetingId(), ip, @@ -473,46 +505,6 @@ public class TemplateService { return result; } - @Transactional(rollbackFor = Exception.class) - public Map downloadWatermark(Long templateId, String watermarkText, String ip, String userAgent) { - TemplateInfo template = findById(templateId); - if ("DISABLED".equalsIgnoreCase(template.getStatus()) || "ARCHIVED".equalsIgnoreCase(template.getStatus())) { - throw new BusinessException(10003, "模板已停用,无法下载"); - } - if (template.getCurrentObjectKey() == null || template.getCurrentObjectKey().trim().isEmpty()) { - throw new BusinessException(10003, "模板当前版本文件不存在"); - } - String signedUrl = ossService.generateDownloadUrl(template.getCurrentObjectKey()); - Long userId = safeUserId(); - assertTemplateDownloadAllowed(template); - if (!template.getWatermarkEnabled()) { - throw new BusinessException(10003, "模板未启用水印下载"); - } - assertTemplateEffectiveNow(template, "水印下载"); - assertDownloadRateLimit(template, userId); - jdbcTemplate.update( - "INSERT INTO template_download_log (tenant_id, template_id, version_no, user_id, object_key, download_type, watermark_text, project_id, meeting_id, ip, user_agent) " + - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - tenantId(), - template.getId(), - template.getCurrentVersionNo(), - userId, - template.getCurrentObjectKey(), - "WATERMARK", - watermarkText == null ? null : watermarkText.trim(), - template.getProjectId(), - template.getMeetingId(), - ip, - userAgent - ); - Map result = new LinkedHashMap(); - result.put("templateId", template.getId()); - result.put("versionNo", template.getCurrentVersionNo()); - result.put("objectKey", template.getCurrentObjectKey()); - result.put("signedUrl", signedUrl); - result.put("watermarkText", watermarkText == null ? "" : watermarkText.trim()); - return result; - } public Map versionDiff(Long templateId, Integer leftVersionNo, Integer rightVersionNo) { assertTemplateExists(templateId); @@ -548,7 +540,6 @@ public class TemplateService { Long userId, String userKeyword, Integer versionNo, - String downloadType, String ip, String downloadedFrom, String downloadedTo, @@ -559,7 +550,6 @@ public class TemplateService { int offset = (safePage - 1) * safeSize; String normalizedTemplateName = trimToNull(templateName); String normalizedUserKeyword = trimToNull(userKeyword); - String normalizedDownloadType = normalizeOptionalDownloadType(downloadType); String normalizedIp = trimToNull(ip); String normalizedDownloadedFrom = trimToNull(downloadedFrom); String normalizedDownloadedTo = trimToNull(downloadedTo); @@ -598,10 +588,7 @@ public class TemplateService { whereSql.append(" AND l.version_no=?"); whereArgs.add(versionNo); } - if (normalizedDownloadType != null) { - whereSql.append(" AND l.download_type=?"); - whereArgs.add(normalizedDownloadType); - } + if (normalizedIp != null) { whereSql.append(" AND l.ip LIKE ?"); whereArgs.add("%" + normalizedIp + "%"); @@ -628,7 +615,7 @@ public class TemplateService { List list = jdbcTemplate.query( "SELECT l.id, l.template_id, COALESCE(t.template_name, '') AS template_name, " + "l.version_no, l.user_id, COALESCE(u.user_name, '') AS user_name, COALESCE(u.phone, '') AS user_phone, " + - "l.object_key, l.download_type, COALESCE(l.watermark_text, '') AS watermark_text, " + + "l.object_key, l.download_type, " + "l.project_id, l.meeting_id, l.ip, l.user_agent, DATE_FORMAT(l.downloaded_at, '%Y-%m-%d %H:%i:%s') AS downloaded_at" + whereSql + " ORDER BY l.downloaded_at DESC, l.id DESC LIMIT ? OFFSET ?", @@ -709,7 +696,7 @@ public class TemplateService { "t.current_version_no, tv.object_key AS current_object_key, " + "DATE_FORMAT(t.effective_from, '%Y-%m-%d %H:%i:%s') AS effective_from, " + "DATE_FORMAT(t.effective_to, '%Y-%m-%d %H:%i:%s') AS effective_to, " + - "t.watermark_enabled, t.download_rate_limit_per_hour, " + + "t.download_rate_limit_per_hour, " + "DATE_FORMAT(t.created_at, '%Y-%m-%d %H:%i:%s') AS created_at, " + "DATE_FORMAT(t.updated_at, '%Y-%m-%d %H:%i:%s') AS updated_at " + "FROM template t " + @@ -806,17 +793,7 @@ public class TemplateService { return normalized; } - private String normalizeOptionalDownloadType(String downloadType) { - String value = trimToNull(downloadType); - if (value == null) { - return null; - } - String normalized = value.toUpperCase(); - if (!"NORMAL".equals(normalized) && !"WATERMARK".equals(normalized)) { - throw new BusinessException(10003, "downloadType仅支持NORMAL/WATERMARK"); - } - return normalized; - } + private String normalizeContentType(String contentType) { if (contentType == null || contentType.trim().isEmpty()) { @@ -936,7 +913,7 @@ public class TemplateService { private void assertTemplateEditable(TemplateInfo template) { if ("ARCHIVED".equalsIgnoreCase(template.getStatus())) { - throw new BusinessException(10003, "已归档模板不允许新增版本"); + throw new BusinessException(10003, "已归档模板不允许编辑或新增版本"); } } diff --git a/backend/src/main/java/com/writeoff/security/PasswordSetupService.java b/backend/src/main/java/com/writeoff/security/PasswordSetupService.java index fcbc751..2aa4831 100644 --- a/backend/src/main/java/com/writeoff/security/PasswordSetupService.java +++ b/backend/src/main/java/com/writeoff/security/PasswordSetupService.java @@ -20,6 +20,7 @@ import java.util.Map; @Service public class PasswordSetupService { private static final String SCENARIO_TENANT_ADMIN_SETUP = "TENANT_ADMIN_SETUP"; + private static final String SCENARIO_USER_SETUP = "USER_SETUP"; private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final JdbcTemplate jdbcTemplate; @@ -71,6 +72,36 @@ public class PasswordSetupService { return buildSetupLink(tenantCode, rawToken); } + @Transactional + public String issueUserSetupLink(Long tenantId, Long userId, Long operatorUserId) { + Map user = loadSystemUser(tenantId, userId); + String tenantCode = String.valueOf(user.get("tenant_code")); + jdbcTemplate.update( + "UPDATE auth_password_setup_token " + + "SET is_deleted=1, updated_by=?, updated_at=CURRENT_TIMESTAMP " + + "WHERE tenant_id=? AND user_id=? AND scenario=? AND is_deleted=0 AND used_at IS NULL", + safeOperator(operatorUserId), + tenantId, + userId, + SCENARIO_USER_SETUP + ); + + String rawToken = generateToken(); + LocalDateTime expiresAt = LocalDateTime.now().plusMinutes(Math.max(passwordSetupExpireMinutes, 10L)); + jdbcTemplate.update( + "INSERT INTO auth_password_setup_token (tenant_id, user_id, scenario, token_hash, expires_at, created_by, updated_by) " + + "VALUES (?, ?, ?, ?, ?, ?, ?)", + tenantId, + userId, + SCENARIO_USER_SETUP, + hashToken(rawToken), + Timestamp.valueOf(expiresAt), + safeOperator(operatorUserId), + safeOperator(operatorUserId) + ); + return buildSetupLink(tenantCode, rawToken); + } + public Map verifyTenantPasswordSetupToken(String tenantCode, String rawToken) { Map tokenRecord = loadAvailableTokenRecord(tenantCode, rawToken); Map data = new LinkedHashMap(); @@ -112,11 +143,12 @@ public class PasswordSetupService { jdbcTemplate.update( "UPDATE auth_password_setup_token " + "SET is_deleted=1, updated_by=?, updated_at=CURRENT_TIMESTAMP " + - "WHERE tenant_id=? AND user_id=? AND scenario=? AND id<>? AND is_deleted=0 AND used_at IS NULL", + "WHERE tenant_id=? AND user_id=? AND scenario IN (?, ?) AND id<>? AND is_deleted=0 AND used_at IS NULL", userId, tenantId, userId, SCENARIO_TENANT_ADMIN_SETUP, + SCENARIO_USER_SETUP, tokenId ); @@ -139,11 +171,12 @@ public class PasswordSetupService { "FROM auth_password_setup_token tkn " + "JOIN tenant t ON tkn.tenant_id=t.id " + "JOIN sys_user u ON tkn.user_id=u.id AND tkn.tenant_id=u.tenant_id " + - "WHERE tkn.token_hash=? AND tkn.scenario=? AND tkn.is_deleted=0 " + + "WHERE tkn.token_hash=? AND tkn.scenario IN (?, ?) AND tkn.is_deleted=0 " + "AND t.is_deleted=0 AND u.is_deleted=0 " + "LIMIT 1", hashToken(normalizedToken), - SCENARIO_TENANT_ADMIN_SETUP + SCENARIO_TENANT_ADMIN_SETUP, + SCENARIO_USER_SETUP ); if (rows.isEmpty()) { throw new BusinessException(10001, "设置链接无效或已过期"); @@ -181,6 +214,22 @@ public class PasswordSetupService { return rows.get(0); } + private Map loadSystemUser(Long tenantId, Long userId) { + List> rows = jdbcTemplate.queryForList( + "SELECT u.id, u.tenant_id, t.tenant_code " + + "FROM sys_user u " + + "JOIN tenant t ON u.tenant_id=t.id " + + "WHERE u.tenant_id=? AND u.id=? AND u.is_deleted=0 AND t.is_deleted=0 " + + "LIMIT 1", + tenantId, + userId + ); + if (rows.isEmpty()) { + throw new BusinessException(10003, "用户不存在"); + } + return rows.get(0); + } + private String buildSetupLink(String tenantCode, String rawToken) { String baseUrl = normalizeBaseUrl(frontendBaseUrl); String path = "/" + tenantCode + "/setup-password?token=" + urlEncode(rawToken); diff --git a/backend/src/main/resources/db/migration/V113__template_update_permission.sql b/backend/src/main/resources/db/migration/V113__template_update_permission.sql new file mode 100644 index 0000000..17c5228 --- /dev/null +++ b/backend/src/main/resources/db/migration/V113__template_update_permission.sql @@ -0,0 +1,27 @@ +-- 添加模板编辑权限 +INSERT INTO permission (id, permission_code, permission_name, module) +SELECT t.next_id, 'template.update', '编辑模板', 'template' +FROM (SELECT IFNULL(MAX(id), 0) + 1 AS next_id FROM permission) t +WHERE NOT EXISTS ( + SELECT 1 FROM permission WHERE permission_code = 'template.update' +); + +-- 授予角色编辑模板权限(通常跟随 template.create 一起授权) +-- 找出所有拥有 template.create 权限的角色,给他们也加上 template.update 权限 +SET @next_rp_id = (SELECT IFNULL(MAX(id), 0) FROM role_permission); + +INSERT INTO role_permission (id, tenant_id, role_id, permission_id) +SELECT + (@next_rp_id := @next_rp_id + 1) AS id, + rp.tenant_id, + rp.role_id, + p.id +FROM role_permission rp +JOIN permission source_p ON source_p.id = rp.permission_id AND source_p.permission_code = 'template.create' +JOIN permission p ON p.permission_code = 'template.update' +WHERE NOT EXISTS ( + SELECT 1 FROM role_permission existing_rp + WHERE existing_rp.tenant_id = rp.tenant_id + AND existing_rp.role_id = rp.role_id + AND existing_rp.permission_id = p.id +); diff --git a/backend/src/main/resources/db/migration/V114__template_view_watermark_permission.sql b/backend/src/main/resources/db/migration/V114__template_view_watermark_permission.sql new file mode 100644 index 0000000..18248c3 --- /dev/null +++ b/backend/src/main/resources/db/migration/V114__template_view_watermark_permission.sql @@ -0,0 +1,51 @@ +-- 添加查看详情和水印下载权限 +INSERT INTO permission (id, permission_code, permission_name, module) +SELECT t.next_id, 'template.detail.read', '查看模板详情', 'template' +FROM (SELECT IFNULL(MAX(id), 0) + 1 AS next_id FROM permission) t +WHERE NOT EXISTS ( + SELECT 1 FROM permission WHERE permission_code = 'template.detail.read' +); + +INSERT INTO permission (id, permission_code, permission_name, module) +SELECT t.next_id, 'template.watermark.download', '水印下载模板', 'template' +FROM (SELECT IFNULL(MAX(id), 0) + 1 AS next_id FROM permission) t +WHERE NOT EXISTS ( + SELECT 1 FROM permission WHERE permission_code = 'template.watermark.download' +); + +-- 授予角色新权限(继承现有权限) +SET @next_rp_id = (SELECT IFNULL(MAX(id), 0) FROM role_permission); + +-- 给有 template.read 的加上 template.detail.read +INSERT INTO role_permission (id, tenant_id, role_id, permission_id) +SELECT + (@next_rp_id := @next_rp_id + 1) AS id, + rp.tenant_id, + rp.role_id, + p.id +FROM role_permission rp +JOIN permission source_p ON source_p.id = rp.permission_id AND source_p.permission_code = 'template.read' +JOIN permission p ON p.permission_code = 'template.detail.read' +WHERE NOT EXISTS ( + SELECT 1 FROM role_permission existing_rp + WHERE existing_rp.tenant_id = rp.tenant_id + AND existing_rp.role_id = rp.role_id + AND existing_rp.permission_id = p.id +); + +-- 给有 template.download 的加上 template.watermark.download +INSERT INTO role_permission (id, tenant_id, role_id, permission_id) +SELECT + (@next_rp_id := @next_rp_id + 1) AS id, + rp.tenant_id, + rp.role_id, + p.id +FROM role_permission rp +JOIN permission source_p ON source_p.id = rp.permission_id AND source_p.permission_code = 'template.download' +JOIN permission p ON p.permission_code = 'template.watermark.download' +WHERE NOT EXISTS ( + SELECT 1 FROM role_permission existing_rp + WHERE existing_rp.tenant_id = rp.tenant_id + AND existing_rp.role_id = rp.role_id + AND existing_rp.permission_id = p.id +); diff --git a/backend/src/main/resources/db/migration/V115__drop_watermark.sql b/backend/src/main/resources/db/migration/V115__drop_watermark.sql new file mode 100644 index 0000000..23c7805 --- /dev/null +++ b/backend/src/main/resources/db/migration/V115__drop_watermark.sql @@ -0,0 +1,7 @@ +-- 移除水印相关字段 +ALTER TABLE template DROP COLUMN watermark_enabled; +ALTER TABLE template_download_log DROP COLUMN watermark_text; + +-- 删除之前新增的 template.watermark.download 权限 +DELETE FROM role_permission WHERE permission_id IN (SELECT id FROM permission WHERE permission_code = 'template.watermark.download'); +DELETE FROM permission WHERE permission_code = 'template.watermark.download'; diff --git a/backend/src/main/resources/db/migration/V116__meeting_update_permission.sql b/backend/src/main/resources/db/migration/V116__meeting_update_permission.sql new file mode 100644 index 0000000..b52565e --- /dev/null +++ b/backend/src/main/resources/db/migration/V116__meeting_update_permission.sql @@ -0,0 +1,27 @@ +SET @next_permission_id := (SELECT IFNULL(MAX(id), 0) + 1 FROM permission); +INSERT INTO permission (id, permission_code, permission_name, module) +SELECT @next_permission_id, 'meeting.update', '编辑会议', 'meeting' +FROM dual +WHERE NOT EXISTS ( + SELECT 1 + FROM permission + WHERE permission_code = 'meeting.update' +); + +SET @next_role_permission_id := (SELECT IFNULL(MAX(id), 0) FROM role_permission); +INSERT INTO role_permission (id, tenant_id, role_id, permission_id) +SELECT + (@next_role_permission_id := @next_role_permission_id + 1) AS id, + rp.tenant_id, + rp.role_id, + p.id AS permission_id +FROM role_permission rp +JOIN permission source_p ON source_p.id = rp.permission_id AND source_p.permission_code = 'meeting.create' +JOIN permission p ON p.permission_code = 'meeting.update' +WHERE NOT EXISTS ( + SELECT 1 + FROM role_permission exist_rp + WHERE exist_rp.tenant_id = rp.tenant_id + AND exist_rp.role_id = rp.role_id + AND exist_rp.permission_id = p.id +); diff --git a/backend/src/main/resources/templates/meeting-summary-template.docx b/backend/src/main/resources/templates/meeting-summary-template.docx index 0abf95d..f1cf17f 100644 Binary files a/backend/src/main/resources/templates/meeting-summary-template.docx and b/backend/src/main/resources/templates/meeting-summary-template.docx differ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 496ff31..41701af 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "writeoff-frontend", "version": "0.0.1", "dependencies": { + "@element-plus/icons-vue": "^2.3.2", "axios": "^1.7.7", "compressorjs": "^1.3.0", "element-plus": "^2.8.4", @@ -17,11 +18,51 @@ "xlsx": "^0.18.5" }, "devDependencies": { + "@types/node": "^25.9.1", "@vitejs/plugin-vue": "^5.1.4", + "@vue/test-utils": "^2.4.11", + "happy-dom": "^20.10.1", + "jsdom": "^27.0.1", "typescript": "^5.6.2", - "vite": "^5.4.8" + "vite": "^5.4.8", + "vitest": "^4.1.8" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", + "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.0.0", + "@csstools/css-color-parser": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.5" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -68,6 +109,146 @@ "node": ">=6.9.0" } }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz", + "integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz", + "integrity": "sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@ctrl/tinycolor": { "version": "4.2.0", "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", @@ -375,6 +556,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", @@ -392,6 +590,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", @@ -409,6 +624,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { "version": "0.21.5", "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", @@ -502,12 +734,48 @@ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@popperjs/core": { "name": "@sxzz/popperjs-es", "version": "2.11.8", @@ -869,6 +1137,31 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmmirror.com/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", @@ -891,12 +1184,39 @@ "@types/lodash": "*" } }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, "node_modules/@types/web-bluetooth": { "version": "0.0.20", "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", "license": "MIT" }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", @@ -911,6 +1231,92 @@ "vue": "^3.2.25" } }, + "node_modules/@vitest/expect": { + "version": "4.1.8", + "resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-4.1.8.tgz", + "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.8", + "resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", + "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.8", + "resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-4.1.8.tgz", + "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.8", + "resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-4.1.8.tgz", + "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.8", + "resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-4.1.8.tgz", + "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.8", + "resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-4.1.8.tgz", + "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vue/compiler-core": { "version": "3.5.29", "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.29.tgz", @@ -1041,6 +1447,27 @@ "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==", "license": "MIT" }, + "node_modules/@vue/test-utils": { + "version": "2.4.11", + "resolved": "https://registry.npmmirror.com/@vue/test-utils/-/test-utils-2.4.11.tgz", + "integrity": "sha512-GDqaqZsA6m2E5vNzej0aYiIb6BX8xV9pNSbbbXKOfEYwg7ZNblVX8suyqmUBThq8VIrgAJNxn+z72hVtUeiWHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^3.0.0" + }, + "peerDependencies": { + "@vue/compiler-dom": "3.x", + "@vue/server-renderer": "3.x", + "vue": "3.x" + }, + "peerDependenciesMeta": { + "@vue/server-renderer": { + "optional": true + } + } + }, "node_modules/@vueuse/core": { "version": "12.0.0", "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-12.0.0.tgz", @@ -1077,6 +1504,16 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/adler-32": { "version": "1.3.1", "resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz", @@ -1086,6 +1523,52 @@ "node": ">=0.8" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/async-validator": { "version": "4.2.5", "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", @@ -1109,6 +1592,23 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/birpc": { "version": "2.9.0", "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", @@ -1124,6 +1624,29 @@ "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==", "license": "MIT" }, + "node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmmirror.com/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -1150,6 +1673,16 @@ "node": ">=0.8" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/codepage": { "version": "1.15.0", "resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz", @@ -1159,6 +1692,26 @@ "node": ">=0.8" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1171,6 +1724,16 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/compressorjs": { "version": "1.3.0", "resolved": "https://registry.npmmirror.com/compressorjs/-/compressorjs-1.3.0.tgz", @@ -1181,6 +1744,24 @@ "is-blob": "^2.1.0" } }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmmirror.com/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/copy-anything": { "version": "4.0.5", "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", @@ -1208,18 +1789,112 @@ "node": ">=0.8" } }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "5.3.7", + "resolved": "https://registry.npmmirror.com/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/data-urls": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/data-urls/-/data-urls-6.0.1.tgz", + "integrity": "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^15.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/dayjs": { "version": "1.11.19", "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", "license": "MIT" }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1243,6 +1918,32 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/editorconfig": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/editorconfig/-/editorconfig-1.0.7.tgz", + "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "^9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/element-plus": { "version": "2.13.5", "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.5.tgz", @@ -1268,6 +1969,13 @@ "vue": "^3.3.0" } }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/entities": { "version": "7.0.1", "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", @@ -1298,6 +2006,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -1370,6 +2085,34 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -1390,6 +2133,23 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", @@ -1476,6 +2236,27 @@ "node": ">= 0.4" } }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmmirror.com/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", @@ -1488,6 +2269,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/happy-dom": { + "version": "20.10.1", + "resolved": "https://registry.npmmirror.com/happy-dom/-/happy-dom-20.10.1.tgz", + "integrity": "sha512-awPoqPjx8CgjapJllyDlgzgVHjBExcitKK5ZJkxwhQJyQpHFkyS2bEcqCm7IeW20cQvuCI0cz2Ifq79CJKqtiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "buffer-image-size": "^0.6.4", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/happy-dom/node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1533,6 +2343,67 @@ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "license": "MIT" }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/is-blob": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/is-blob/-/is-blob-2.1.0.tgz", @@ -1545,6 +2416,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-what": { "version": "5.5.0", "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", @@ -1557,6 +2445,98 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmmirror.com/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-cookie": { + "version": "3.0.8", + "resolved": "https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "27.0.1", + "resolved": "https://registry.npmmirror.com/jsdom/-/jsdom-27.0.1.tgz", + "integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/dom-selector": "^6.7.2", + "cssstyle": "^5.3.1", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/lodash": { "version": "4.17.23", "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz", @@ -1580,6 +2560,16 @@ "lodash-es": "*" } }, + "node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", @@ -1598,6 +2588,13 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/memoize-one": { "version": "6.0.0", "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", @@ -1625,12 +2622,45 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mitt": { "version": "3.0.1", "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "license": "MIT" }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", @@ -1649,12 +2679,116 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmmirror.com/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/normalize-wheel-es": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", "license": "BSD-3-Clause" }, + "node_modules/obug": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/obug/-/obug-2.1.2.tgz", + "integrity": "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -1667,6 +2801,19 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pinia": { "version": "3.0.4", "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", @@ -1725,12 +2872,39 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", @@ -1782,6 +2956,89 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1812,6 +3069,124 @@ "node": ">=0.8" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/superjson": { "version": "2.2.6", "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", @@ -1824,6 +3199,103 @@ "node": ">=16" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.2", + "resolved": "https://registry.npmmirror.com/tldts/-/tldts-7.4.2.tgz", + "integrity": "sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.2" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.2", + "resolved": "https://registry.npmmirror.com/tldts-core/-/tldts-core-7.4.2.tgz", + "integrity": "sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", @@ -1838,6 +3310,13 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", @@ -1898,6 +3377,641 @@ } } }, + "node_modules/vitest": { + "version": "4.1.8", + "resolved": "https://registry.npmmirror.com/vitest/-/vitest-4.1.8.tgz", + "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.8", + "resolved": "https://registry.npmmirror.com/@vitest/mocker/-/mocker-4.1.8.tgz", + "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/vitest/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/vue": { "version": "3.5.29", "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.29.tgz", @@ -1919,6 +4033,13 @@ } } }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/vue-component-type-helpers/-/vue-component-type-helpers-3.3.3.tgz", + "integrity": "sha512-x4nsFpy5Pe8fqPzp/5vkTPeTTDBpAx4WVtV47Ejt0+2FQrq4pRRsJs7JmYRqMFzTu/LW+pCWEjQ3YVCkPV7f9g==", + "dev": true, + "license": "MIT" + }, "node_modules/vue-router": { "version": "4.6.4", "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", @@ -1934,6 +4055,100 @@ "vue": "^3.5.0" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wmf": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz", @@ -1952,6 +4167,126 @@ "node": ">=0.8" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xlsx": { "version": "0.18.5", "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz", @@ -1972,6 +4307,23 @@ "engines": { "node": ">=0.8" } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" } } } diff --git a/frontend/package.json b/frontend/package.json index cf0c4af..1bebfa4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,9 +6,11 @@ "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest" }, "dependencies": { + "@element-plus/icons-vue": "^2.3.2", "axios": "^1.7.7", "compressorjs": "^1.3.0", "element-plus": "^2.8.4", @@ -18,8 +20,13 @@ "xlsx": "^0.18.5" }, "devDependencies": { + "@types/node": "^25.9.1", "@vitejs/plugin-vue": "^5.1.4", + "@vue/test-utils": "^2.4.11", + "happy-dom": "^20.10.1", + "jsdom": "^27.0.1", "typescript": "^5.6.2", - "vite": "^5.4.8" + "vite": "^5.4.8", + "vitest": "^4.1.8" } } diff --git a/frontend/src/api/modules.ts b/frontend/src/api/modules.ts index d9734d9..16d13b1 100644 --- a/frontend/src/api/modules.ts +++ b/frontend/src/api/modules.ts @@ -716,12 +716,23 @@ export const fetchTemplates = (params?: { status?: string; scopeType?: string; bizScene?: string; - watermarkEnabled?: boolean; + effectiveStatus?: string; pageNo?: number; pageSize?: number; }) => http.get("/templates", { params }); +export const fetchTemplateViewList = (params?: { + templateName?: string; + templateType?: string; + scopeType?: string; + bizScene?: string; + + effectiveStatus?: string; + pageNo?: number; + pageSize?: number; +}) => + http.get("/templates/view-list", { params }); export const fetchPublishedTemplateOptions = (params?: { bizScene?: string }) => http.get("/templates/published-options", { params }); export const fetchTemplateTypeOptions = () => http.get("/templates/type-options"); @@ -742,9 +753,21 @@ export const createTemplate = (payload: { changeLog?: string; effectiveFrom?: string; effectiveTo?: string; - watermarkEnabled?: boolean; + downloadRateLimitPerHour?: number; }) => http.post("/templates", payload); +export const updateTemplate = (id: number, payload: { + templateName: string; + templateType: string; + scopeType: "ALL" | "PROJECT" | "MEETING"; + projectId?: number; + meetingId?: number; + bizScene?: "MEETING_RECOMMEND" | "AUDIT_NOTIFY" | "SETTLEMENT"; + effectiveFrom?: string; + effectiveTo?: string; + + downloadRateLimitPerHour?: number; +}) => http.post(`/templates/${id}/update`, payload); export const fetchTemplateUploadSign = (payload: { fileName: string; contentType?: string; @@ -764,8 +787,6 @@ export const archiveTemplate = (id: number) => http.post(`/templates/${id}/archi export const rollbackTemplate = (id: number, payload: { versionNo: number; rollbackReason: string }) => http.post(`/templates/${id}/rollback`, payload); export const downloadTemplate = (id: number) => http.get(`/templates/${id}/download`); -export const downloadTemplateWatermark = (id: number, params?: { watermarkText?: string }) => - http.get(`/templates/${id}/download-watermark`, { params }); export const fetchTemplateVersionDiff = (id: number, params?: { leftVersionNo?: number; rightVersionNo?: number }) => http.get(`/templates/${id}/versions/diff`, { params }); export const fetchTemplateDownloadLogs = (params?: { @@ -774,7 +795,7 @@ export const fetchTemplateDownloadLogs = (params?: { userId?: number; userKeyword?: string; versionNo?: number; - downloadType?: "NORMAL" | "WATERMARK"; + ip?: string; downloadedFrom?: string; downloadedTo?: string; diff --git a/frontend/src/components/InAppNotificationDetailDialog.vue b/frontend/src/components/InAppNotificationDetailDialog.vue index bcfe62e..a0ec0b0 100644 --- a/frontend/src/components/InAppNotificationDetailDialog.vue +++ b/frontend/src/components/InAppNotificationDetailDialog.vue @@ -76,6 +76,7 @@ const handleMarkRead = () => { align-items: flex-start; justify-content: space-between; gap: 12px; + margin-top: 10px; } .notif-detail-title { diff --git a/frontend/src/constants/permissions.ts b/frontend/src/constants/permissions.ts index c61c5f4..95dce6b 100644 --- a/frontend/src/constants/permissions.ts +++ b/frontend/src/constants/permissions.ts @@ -13,6 +13,7 @@ export const PERMS = { read: "meeting.read", manage: "meeting.manage", create: "meeting.create", + update: "meeting.update", delete: "meeting.delete", cancel: "meeting.cancel", submit: "meeting.submit", @@ -83,6 +84,7 @@ export const PERMS = { read: "template.read", manage: "template.manage", create: "template.create", + update: "template.update", publish: "template.publish", disable: "template.disable", archive: "template.archive", @@ -90,6 +92,7 @@ export const PERMS = { download: "template.download", downloadLogReadAll: "template.download.log.read.all", flowLink: "template.flow.link", + detailRead: "template.detail.read", }, expert: { read: "expert.read", diff --git a/frontend/src/utils/batchImport.ts b/frontend/src/utils/batchImport.ts index 0df6c78..b4182d1 100644 --- a/frontend/src/utils/batchImport.ts +++ b/frontend/src/utils/batchImport.ts @@ -1,11 +1,47 @@ +import * as xlsx from "xlsx"; + export const readTextFile = (file: File): Promise => new Promise((resolve, reject) => { const reader = new FileReader(); - reader.onload = () => resolve(String(reader.result || "")); + reader.onload = () => { + const buffer = reader.result as ArrayBuffer; + try { + const decoderUtf8 = new TextDecoder("utf-8", { fatal: true }); + resolve(decoderUtf8.decode(buffer)); + } catch (e) { + try { + const decoderGbk = new TextDecoder("gbk"); + resolve(decoderGbk.decode(buffer)); + } catch (e2) { + reject(new Error("文件编码无法识别")); + } + } + }; reader.onerror = () => reject(reader.error || new Error("文件读取失败")); - reader.readAsText(file, "utf-8"); + reader.readAsArrayBuffer(file); }); +export const readXlsxFile = async (file: File): Promise => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = (e) => { + try { + const data = e.target?.result; + // codepage 936 tells xlsx to use GBK for CSVs misnamed as XLS + const workbook = xlsx.read(data, { type: "array", codepage: 936 }); + const firstSheetName = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[firstSheetName]; + const csv = xlsx.utils.sheet_to_csv(worksheet); + resolve(csv); + } catch (err) { + reject(new Error("读取 Excel 文件失败")); + } + }; + reader.onerror = () => reject(reader.error || new Error("文件读取失败")); + reader.readAsArrayBuffer(file); + }); +}; + export const downloadCsvTemplate = (fileName: string, headers: string[], sampleRows: string[][] = []) => { downloadCsvRows(fileName, [headers, ...sampleRows]); }; diff --git a/frontend/src/utils/status.ts b/frontend/src/utils/status.ts index b388e4f..68a5177 100644 --- a/frontend/src/utils/status.ts +++ b/frontend/src/utils/status.ts @@ -65,6 +65,7 @@ const STATUS_TEXT_MAP: Record = { AUDIT_RETURNED: "审核退回", FINANCE_CONFIRMED: "财务已确认", USER_CREATED: "用户创建", + USER_PASSWORD_RESET: "密码重置", DELIVERED: "已送达", UNREAD: "未读", READ: "已读", diff --git a/frontend/src/views/layout/AppLayout.vue b/frontend/src/views/layout/AppLayout.vue index bd11f10..ab6f5c7 100644 --- a/frontend/src/views/layout/AppLayout.vue +++ b/frontend/src/views/layout/AppLayout.vue @@ -64,7 +64,8 @@ 工作台 个人设置 - 退出全部设备 + 刷新权限 + 退出登录 @@ -159,7 +160,8 @@ 工作台 个人设置 - 退出全部设备 + 刷新权限 + 退出登录 @@ -281,7 +283,7 @@ import { useRoute, useRouter } from "vue-router"; import BreadcrumbNav from "../../components/BreadcrumbNav.vue"; import GlobalSearchLauncher from "../../components/GlobalSearchLauncher.vue"; import InAppNotificationDetailDialog from "../../components/InAppNotificationDetailDialog.vue"; -import { fetchInAppNotifications, markAllInAppNotificationsRead, markInAppNotificationRead, logoutAllAuth, switchTenant } from "../../api/modules"; +import { fetchInAppNotifications, markAllInAppNotificationsRead, markInAppNotificationRead, logoutAllAuth, switchTenant, refreshAuth } from "../../api/modules"; import { PERMS } from "../../constants/permissions"; import { useAppearanceStore } from "../../stores/appearance"; import { useAuthStore } from "../../stores/auth"; @@ -554,6 +556,19 @@ const handleUserCommand = async (command: string) => { await router.push(profileRoute.value); return; } + if (command === "refreshAuth") { + try { + const resp = await refreshAuth(); + withSuppressedAuthRefresh(() => { + authStore.saveAuthPayload(resp?.data || null); + }); + await refreshLayoutState(); + ElMessage.success("权限刷新成功"); + } catch (e) { + // Ignore, handled by global interceptor + } + return; + } if (command === "logout") { await logoutAll(); } diff --git a/frontend/src/views/modules/AuditFlowPage.vue b/frontend/src/views/modules/AuditFlowPage.vue index 6ea8c39..543edb6 100644 --- a/frontend/src/views/modules/AuditFlowPage.vue +++ b/frontend/src/views/modules/AuditFlowPage.vue @@ -29,7 +29,7 @@ - + - - - + - +