diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 528f847..16bb161 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -179,41 +179,51 @@ }, "active": "75e85cb517113c51", "lastOpenFiles": [ - "docs/10-验收检查清单.md", - "docs/05-API接口契约.md", - "docs/02-开发计划.md", - "sql/migrations/V1__initial_schema.sql", - "docs/03-数据库设计文档.md", - "scripts/test.sh", - "scripts/setup-test-db.sh", - "sql/migrations", - "sql", - "scripts", - "docs/09-项目目录结构.md", - "docs/07-环境配置.md", - "docs/06-UI交互规范.md", - "docs/01-需求清单/MOD-模块管理/REQ-MOD-004.md", - "docs/01-需求清单/MOD-模块管理/REQ-MOD-003.md", - "docs/01-需求清单/MOD-模块管理/_module.md", - "docs/01-需求清单/MOD-模块管理/REQ-MOD-002.md", - "docs/01-需求清单/MOD-模块管理/REQ-MOD-001.md", - "docs/01-需求清单/MOD-模块管理", - "docs/01-需求清单/USR-用户管理/_module.md", - "docs/01-需求清单/USR-用户管理/REQ-USR-004.md", - "docs/01-需求清单/USR-用户管理/REQ-USR-003.md", - "docs/01-需求清单/USR-用户管理/REQ-USR-002.md", - "docs/01-需求清单/USR-用户管理/REQ-USR-001.md", - "docs/01-需求清单/USR-用户管理", - "CLAUDE.md", - "docs/01-需求清单/index.md", - "docs/08-模块任务管理.md", - "docs/04-技术规范.md", - "docs/01-需求清单", - "docs", - "docs/superpowers/plans/2026-04-21-REQ-SYS-001.md", - "docs/superpowers/module-reports/module_sys-cross-module.md", - "docs/01-需求清单/SYS-系统管理.md", - "docs/01-需求清单/README.md", - "docs/superpowers/reviews/2026-04-21-REQ-SYS-001.md" + "backend/target/surefire/surefirebooter-20260430195247431_3.jar", + "backend/target/surefire/surefire_0-20260430195247431_2tmp", + "backend/target/surefire/surefire-20260430195247431_1tmp", + "backend/target/surefire", + "backend/target/surefire/surefirebooter-20260430195125577_3.jar", + "backend/target/surefire/surefire_0-20260430195125577_2tmp", + "backend/target/surefire/surefire-20260430195125577_1tmp", + "sql/migrations/V2__bool_columns_to_bit.sql", + "frontend/dist/assets/index-C0VJAYdp.js", + "frontend/dist/assets/index-Bml_u6eA.js", + "frontend/dist/assets/index-DYC1LGAT.js", + "frontend/node_modules/antd/README.md", + "frontend/node_modules/@ant-design/icons/README.md", + "frontend/node_modules/@ant-design/icons/docs/demo/use-iconfontcn.md", + "frontend/node_modules/@ant-design/icons/docs/demo/two-tone.md", + "frontend/node_modules/@ant-design/icons/docs/demo/tooltip.md", + "frontend/node_modules/@ant-design/icons/docs/demo/simple.md", + "frontend/node_modules/@ant-design/icons/docs/demo/root-class.md", + "frontend/node_modules/@ant-design/icons/docs/demo/loadModules.md", + "frontend/node_modules/@ant-design/icons/docs/demo/custom-icon.md", + "frontend/node_modules/@ant-design/icons/docs/demo/basic.md", + "frontend/node_modules/@ant-design/icons/docs/demo/ant-design-twotone-demo.md", + "frontend/node_modules/@ant-design/icons/docs/demo/all-icons.md", + "frontend/node_modules/@ant-design/icons-svg/inline-svg/outlined/zoom-out.svg", + "frontend/node_modules/@ant-design/icons-svg/inline-svg/outlined/zoom-in.svg", + "frontend/node_modules/@ant-design/icons-svg/inline-svg/outlined/zhihu.svg", + "frontend/node_modules/@ant-design/icons-svg/inline-svg/outlined/yuque.svg", + "frontend/node_modules/@ant-design/icons-svg/inline-svg/outlined/youtube.svg", + "frontend/node_modules/@ant-design/icons-svg/inline-svg/outlined/yahoo.svg", + "frontend/node_modules/@ant-design/icons-svg/inline-svg/outlined/x.svg", + "frontend/node_modules/@ant-design/icons-svg/inline-svg/outlined/woman.svg", + "frontend/node_modules/@ant-design/icons-svg/inline-svg/outlined/windows.svg", + "frontend/node_modules/@ant-design/icons-svg/inline-svg/outlined/wifi.svg", + "frontend/node_modules/@ant-design/icons-svg/ReadMe.md", + "frontend/node_modules/rc-drawer/README.md", + "frontend/node_modules/@reduxjs/toolkit/README.md", + "frontend/node_modules/dayjs/README.md", + "frontend/node_modules/dayjs/CHANGELOG.md", + "frontend/node_modules/react-redux/README.md", + "frontend/node_modules/react-redux/LICENSE.md", + "frontend/node_modules/axios/README.md", + "frontend/node_modules/axios/MIGRATION_GUIDE.md", + "frontend/node_modules/axios/CHANGELOG.md", + "frontend/node_modules/@ant-design/colors/README.md", + "frontend/node_modules/axios/lib/helpers/README.md", + "frontend/node_modules/axios/lib/env/README.md" ] } \ No newline at end of file diff --git a/backend/src/main/java/com/xly/erp/common/config/CorsConfig.java b/backend/src/main/java/com/xly/erp/common/config/CorsConfig.java new file mode 100644 index 0000000..bbdc177 --- /dev/null +++ b/backend/src/main/java/com/xly/erp/common/config/CorsConfig.java @@ -0,0 +1,31 @@ +package com.xly.erp.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; + +@Configuration +public class CorsConfig { + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration cfg = new CorsConfiguration(); + cfg.setAllowedOrigins(List.of( + "http://localhost:5173", + "http://127.0.0.1:5173" + )); + cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); + cfg.setAllowedHeaders(List.of("*")); + cfg.setExposedHeaders(List.of("Authorization")); + cfg.setAllowCredentials(true); + cfg.setMaxAge(3600L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/api/**", cfg); + return source; + } +} diff --git a/backend/src/main/java/com/xly/erp/common/security/SecurityConfig.java b/backend/src/main/java/com/xly/erp/common/security/SecurityConfig.java index 02e9f31..3ba915d 100644 --- a/backend/src/main/java/com/xly/erp/common/security/SecurityConfig.java +++ b/backend/src/main/java/com/xly/erp/common/security/SecurityConfig.java @@ -4,6 +4,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; @@ -24,8 +25,10 @@ public class SecurityConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) + .cors(Customizer.withDefaults()) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth + .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers(HttpMethod.POST, "/api/usr/auth/login").permitAll() .anyRequest().authenticated() ) diff --git a/backend/src/main/java/com/xly/erp/module/usr/controller/StaffController.java b/backend/src/main/java/com/xly/erp/module/usr/controller/StaffController.java new file mode 100644 index 0000000..897ee9a --- /dev/null +++ b/backend/src/main/java/com/xly/erp/module/usr/controller/StaffController.java @@ -0,0 +1,33 @@ +package com.xly.erp.module.usr.controller; + +import com.xly.erp.common.response.Result; +import com.xly.erp.module.usr.mapper.StaffMapper; +import com.xly.erp.module.usr.vo.StaffSearchVO; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api/usr") +public class StaffController { + + private static final int DEFAULT_LIMIT = 20; + private static final int MAX_LIMIT = 50; + + private final StaffMapper staffMapper; + + public StaffController(StaffMapper staffMapper) { + this.staffMapper = staffMapper; + } + + @GetMapping("/staffs") + public Result> search(@RequestParam(required = false) String keyword, + @RequestParam(required = false) Integer limit) { + int n = (limit == null || limit < 1) ? DEFAULT_LIMIT : Math.min(limit, MAX_LIMIT); + String kw = keyword == null ? "" : keyword.trim(); + return Result.ok(staffMapper.searchActive(kw, n)); + } +} diff --git a/backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java b/backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java index 3d59c63..fa8b808 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java +++ b/backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java @@ -4,6 +4,8 @@ import com.xly.erp.common.response.Result; import com.xly.erp.module.usr.dto.CreateUserDTO; import com.xly.erp.module.usr.dto.UpdateUserDTO; import com.xly.erp.module.usr.service.UserService; +import com.xly.erp.module.usr.vo.PermissionCategoryVO; +import com.xly.erp.module.usr.vo.UserListVO; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -14,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; import java.util.Map; @RestController @@ -46,4 +49,14 @@ public class UserController { @RequestParam(required = false) Integer pageSize) { return Result.ok(userService.list(field, match, value, pageNum, pageSize)); } + + @GetMapping("/users/{id}") + public Result detail(@PathVariable Integer id) { + return Result.ok(userService.detail(id)); + } + + @GetMapping("/permission-categories") + public Result> permissionCategories() { + return Result.ok(userService.listPermissionCategories()); + } } diff --git a/backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java b/backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java index 171b13c..cde6145 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java +++ b/backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java @@ -1,5 +1,6 @@ package com.xly.erp.module.usr.mapper; +import com.xly.erp.module.usr.vo.PermissionCategoryVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; @@ -15,4 +16,10 @@ public interface PermissionCategoryMapper { + "#{id}" + "") int countActiveByIds(@Param("ids") List ids); + + @Select("SELECT iIncrement, sCategoryCode, sCategoryName, iParentId, iSortOrder " + + "FROM tPermissionCategory " + + "WHERE bDeleted = 0 " + + "ORDER BY iSortOrder ASC, iIncrement ASC") + List listActive(); } diff --git a/backend/src/main/java/com/xly/erp/module/usr/mapper/StaffMapper.java b/backend/src/main/java/com/xly/erp/module/usr/mapper/StaffMapper.java index e06946f..5b826eb 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/mapper/StaffMapper.java +++ b/backend/src/main/java/com/xly/erp/module/usr/mapper/StaffMapper.java @@ -1,9 +1,12 @@ package com.xly.erp.module.usr.mapper; +import com.xly.erp.module.usr.vo.StaffSearchVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; +import java.util.List; + @Mapper public interface StaffMapper { @@ -13,4 +16,19 @@ public interface StaffMapper { default boolean existsActiveById(Integer id) { return findActiveStaffFlag(id) != null; } + + @Select(""" + + """) + List searchActive(@Param("keyword") String keyword, @Param("limit") Integer limit); } diff --git a/backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java b/backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java index f65f759..3372ad7 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java +++ b/backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java @@ -31,4 +31,6 @@ public interface UserMapper extends BaseMapper { long countWithFilter(@Param("field") String field, @Param("matchOp") String matchOp, @Param("value") Object value); + + UserListVO selectDetailById(@Param("id") Integer id); } diff --git a/backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java b/backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java index 81f765e..9952d09 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java +++ b/backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java @@ -4,9 +4,18 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xly.erp.module.usr.entity.UserPermission; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; public interface UserPermissionMapper extends BaseMapper { @Delete("DELETE FROM tUserPermission WHERE iUserId = #{userId}") int deleteByUserId(@Param("userId") Integer userId); + + @Select("SELECT up.iCategoryId FROM tUserPermission up " + + "INNER JOIN tPermissionCategory pc ON pc.iIncrement = up.iCategoryId " + + "WHERE up.iUserId = #{userId} AND pc.bDeleted = 0 " + + "ORDER BY up.iIncrement ASC") + List selectCategoryIdsByUserId(@Param("userId") Integer userId); } diff --git a/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java b/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java index a7d0ec1..b1ac9e7 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java +++ b/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java @@ -4,7 +4,10 @@ import com.xly.erp.module.usr.dto.CreateUserDTO; import com.xly.erp.module.usr.dto.LoginDTO; import com.xly.erp.module.usr.dto.UpdateUserDTO; import com.xly.erp.module.usr.vo.LoginVO; +import com.xly.erp.module.usr.vo.PermissionCategoryVO; +import com.xly.erp.module.usr.vo.UserListVO; +import java.util.List; import java.util.Map; public interface UserService { @@ -14,5 +17,9 @@ public interface UserService { Map list(String field, String match, String value, Integer pageNum, Integer pageSize); + UserListVO detail(Integer id); + + List listPermissionCategories(); + LoginVO login(LoginDTO dto); } diff --git a/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java b/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java index 0c7df8a..48d4e1d 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java +++ b/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java @@ -11,6 +11,7 @@ import com.xly.erp.module.usr.dto.UpdateUserDTO; import com.xly.erp.module.usr.entity.User; import com.xly.erp.module.usr.security.LoginAttemptStore; import com.xly.erp.module.usr.vo.LoginVO; +import com.xly.erp.module.usr.vo.PermissionCategoryVO; import com.xly.erp.module.usr.vo.UserBriefVO; import com.xly.erp.module.usr.vo.UserListVO; import com.xly.erp.module.usr.entity.UserPermission; @@ -259,6 +260,23 @@ public class UserServiceImpl implements UserService { return result; } + @Override + @Transactional(readOnly = true) + public UserListVO detail(Integer id) { + UserListVO vo = userMapper.selectDetailById(id); + if (vo == null || Boolean.TRUE.equals(vo.getBDeleted())) { + throw new BizException(40400, "用户不存在或已删除"); + } + vo.setPermissionCategoryIds(userPermissionMapper.selectCategoryIdsByUserId(id)); + return vo; + } + + @Override + @Transactional(readOnly = true) + public List listPermissionCategories() { + return permissionCategoryMapper.listActive(); + } + private Integer parseBoolean(String v) { return switch (v.toLowerCase()) { case "true", "1" -> 1; diff --git a/backend/src/main/java/com/xly/erp/module/usr/vo/PermissionCategoryVO.java b/backend/src/main/java/com/xly/erp/module/usr/vo/PermissionCategoryVO.java new file mode 100644 index 0000000..5b5fd75 --- /dev/null +++ b/backend/src/main/java/com/xly/erp/module/usr/vo/PermissionCategoryVO.java @@ -0,0 +1,32 @@ +package com.xly.erp.module.usr.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class PermissionCategoryVO { + + @JsonProperty("iIncrement") + private Integer iIncrement; + + @JsonProperty("sCategoryCode") + private String sCategoryCode; + + @JsonProperty("sCategoryName") + private String sCategoryName; + + @JsonProperty("iParentId") + private Integer iParentId; + + @JsonProperty("iSortOrder") + private Integer iSortOrder; + + public Integer getIIncrement() { return iIncrement; } + public void setIIncrement(Integer iIncrement) { this.iIncrement = iIncrement; } + public String getSCategoryCode() { return sCategoryCode; } + public void setSCategoryCode(String sCategoryCode) { this.sCategoryCode = sCategoryCode; } + public String getSCategoryName() { return sCategoryName; } + public void setSCategoryName(String sCategoryName) { this.sCategoryName = sCategoryName; } + public Integer getIParentId() { return iParentId; } + public void setIParentId(Integer iParentId) { this.iParentId = iParentId; } + public Integer getISortOrder() { return iSortOrder; } + public void setISortOrder(Integer iSortOrder) { this.iSortOrder = iSortOrder; } +} diff --git a/backend/src/main/java/com/xly/erp/module/usr/vo/StaffSearchVO.java b/backend/src/main/java/com/xly/erp/module/usr/vo/StaffSearchVO.java new file mode 100644 index 0000000..e97a580 --- /dev/null +++ b/backend/src/main/java/com/xly/erp/module/usr/vo/StaffSearchVO.java @@ -0,0 +1,27 @@ +package com.xly.erp.module.usr.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class StaffSearchVO { + + @JsonProperty("iIncrement") + private Integer iIncrement; + + @JsonProperty("sStaffNo") + private String sStaffNo; + + @JsonProperty("sStaffName") + private String sStaffName; + + @JsonProperty("sDepartment") + private String sDepartment; + + public Integer getIIncrement() { return iIncrement; } + public void setIIncrement(Integer iIncrement) { this.iIncrement = iIncrement; } + public String getSStaffNo() { return sStaffNo; } + public void setSStaffNo(String sStaffNo) { this.sStaffNo = sStaffNo; } + public String getSStaffName() { return sStaffName; } + public void setSStaffName(String sStaffName) { this.sStaffName = sStaffName; } + public String getSDepartment() { return sDepartment; } + public void setSDepartment(String sDepartment) { this.sDepartment = sDepartment; } +} diff --git a/backend/src/main/java/com/xly/erp/module/usr/vo/UserListVO.java b/backend/src/main/java/com/xly/erp/module/usr/vo/UserListVO.java index 04bf148..ddf6a63 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/vo/UserListVO.java +++ b/backend/src/main/java/com/xly/erp/module/usr/vo/UserListVO.java @@ -3,6 +3,7 @@ package com.xly.erp.module.usr.vo; import com.fasterxml.jackson.annotation.JsonProperty; import java.time.LocalDateTime; +import java.util.List; public class UserListVO { @@ -12,6 +13,9 @@ public class UserListVO { @JsonProperty("sUserName") private String sUserName; + @JsonProperty("iStaffId") + private Integer iStaffId; + @JsonProperty("staffName") private String staffName; @@ -27,6 +31,12 @@ public class UserListVO { @JsonProperty("sLanguage") private String sLanguage; + @JsonProperty("bCanModifyDocs") + private Boolean bCanModifyDocs; + + @JsonProperty("permissionCategoryIds") + private List permissionCategoryIds; + @JsonProperty("bDeleted") private Boolean bDeleted; @@ -43,6 +53,8 @@ public class UserListVO { public void setIIncrement(Integer iIncrement) { this.iIncrement = iIncrement; } public String getSUserName() { return sUserName; } public void setSUserName(String sUserName) { this.sUserName = sUserName; } + public Integer getIStaffId() { return iStaffId; } + public void setIStaffId(Integer iStaffId) { this.iStaffId = iStaffId; } public String getStaffName() { return staffName; } public void setStaffName(String staffName) { this.staffName = staffName; } public String getSUserNo() { return sUserNo; } @@ -53,6 +65,10 @@ public class UserListVO { public void setSUserType(String sUserType) { this.sUserType = sUserType; } public String getSLanguage() { return sLanguage; } public void setSLanguage(String sLanguage) { this.sLanguage = sLanguage; } + public Boolean getBCanModifyDocs() { return bCanModifyDocs; } + public void setBCanModifyDocs(Boolean bCanModifyDocs) { this.bCanModifyDocs = bCanModifyDocs; } + public List getPermissionCategoryIds() { return permissionCategoryIds; } + public void setPermissionCategoryIds(List permissionCategoryIds) { this.permissionCategoryIds = permissionCategoryIds; } public Boolean getBDeleted() { return bDeleted; } public void setBDeleted(Boolean bDeleted) { this.bDeleted = bDeleted; } public LocalDateTime getTLastLoginDate() { return tLastLoginDate; } diff --git a/backend/src/main/resources/mapper/usr/UserMapper.xml b/backend/src/main/resources/mapper/usr/UserMapper.xml index 28caade..74d2c10 100644 --- a/backend/src/main/resources/mapper/usr/UserMapper.xml +++ b/backend/src/main/resources/mapper/usr/UserMapper.xml @@ -6,11 +6,13 @@ u.iIncrement AS iIncrement, u.sUserName AS sUserName, + u.iStaffId AS iStaffId, s.sStaffName AS staffName, u.sUserNo AS sUserNo, s.sDepartment AS department, u.sUserType AS sUserType, u.sLanguage AS sLanguage, + u.bCanModifyDocs AS bCanModifyDocs, u.bDeleted AS bDeleted, u.tLastLoginDate AS tLastLoginDate, u.sCreatedBy AS sCreatedBy, @@ -45,4 +47,12 @@ + + diff --git a/docs/03-数据库设计文档.md b/docs/03-数据库设计文档.md index fe53b37..e497740 100644 --- a/docs/03-数据库设计文档.md +++ b/docs/03-数据库设计文档.md @@ -56,11 +56,11 @@ Migration 清单: `sql/migrations/V*.sql`(由 Flyway 顺序 apply) | `iStaffId` | int | 是 | NULL | 关联职员 ID(可选,外键 → `tStaff.iIncrement`) | | `sUserType` | varchar(20) | 否 | `普通用户` | 用户类型;枚举:`普通用户` / `超级管理员` | | `sLanguage` | varchar(10) | 否 | `zh` | 语言偏好;枚举:`zh` / `en` / `zh-TW` | -| `bCanModifyDocs` | tinyint(1) | 否 | 0 | 单据修改权限;0 否 / 1 是 | +| `bCanModifyDocs` | bit(1) | 否 | 0 | 单据修改权限;0 否 / 1 是 | | `sPasswordHash` | varchar(255) | 否 | — | 密码哈希值(BCrypt 等强哈希算法),新增默认初始密码 `666666` 的哈希 | | `tLastLoginDate` | datetime | 是 | NULL | 最后登录时间 | | `sCreatedBy` | varchar(50) | 是 | — | 制单人(创建用户的操作员用户号) | -| `bDeleted` | tinyint(1) | 否 | 0 | 软删除标记;0 有效 / 1 已作废 | +| `bDeleted` | bit(1) | 否 | 0 | 软删除标记;0 有效 / 1 已作废 | | `tDeletedDate` | datetime | 是 | NULL | 软删除时间 | | `sDeletedBy` | varchar(50) | 是 | NULL | 软删除操作人 | @@ -100,7 +100,7 @@ Migration 清单: `sql/migrations/V*.sql`(由 Flyway 顺序 apply) | `sStaffName` | varchar(50) | 否 | — | 职员姓名 | | `sDepartment` | varchar(100) | 是 | NULL | 所属部门(本期暂用字符串,未来如需独立 `tDepartment` 字典表再另行重构) | | `sCreatedBy` | varchar(50) | 是 | — | 制单人 | -| `bDeleted` | tinyint(1) | 否 | 0 | 软删除标记 | +| `bDeleted` | bit(1) | 否 | 0 | 软删除标记 | | `tDeletedDate` | datetime | 是 | NULL | 软删除时间 | | `sDeletedBy` | varchar(50) | 是 | NULL | 软删除操作人 | @@ -137,7 +137,7 @@ Migration 清单: `sql/migrations/V*.sql`(由 Flyway 顺序 apply) | `iParentId` | int | 是 | NULL | 父分类 ID(自引用,根节点为 NULL) | | `iSortOrder` | int | 否 | 0 | 同级排序号 | | `sCreatedBy` | varchar(50) | 是 | — | 制单人 | -| `bDeleted` | tinyint(1) | 否 | 0 | 软删除标记 | +| `bDeleted` | bit(1) | 否 | 0 | 软删除标记 | | `tDeletedDate` | datetime | 是 | NULL | 软删除时间 | | `sDeletedBy` | varchar(50) | 是 | NULL | 软删除操作人 | @@ -204,12 +204,12 @@ Migration 清单: `sql/migrations/V*.sql`(由 Flyway 顺序 apply) | `sProcedureName` | varchar(100) | 否 | — | 存储过程(审核)名称;系统内唯一 | | `sModuleType` | varchar(50) | 否 | — | 模块类型(本期按自由文本处理,VARCHAR(50);如未来收敛到枚举再加 CHECK 约束) | | `sManageDeptEn` | varchar(50) | 否 | — | 管理部门英文标识 | -| `bShowPermission` | tinyint(1) | 否 | 0 | 权限是否显示;0 否 / 1 是 | +| `bShowPermission` | bit(1) | 否 | 0 | 权限是否显示;0 否 / 1 是 | | `sModuleNameZh` | varchar(100) | 否 | — | 界面名称(中文,模糊查询用) | | `iParentId` | int | 是 | NULL | 父模块 ID(自引用,根节点为 NULL) | | `iSortOrder` | int | 否 | 0 | 同级排序号 | | `sCreatedBy` | varchar(50) | 是 | — | 制单人 | -| `bDeleted` | tinyint(1) | 否 | 0 | 软删除标记 | +| `bDeleted` | bit(1) | 否 | 0 | 软删除标记 | | `tDeletedDate` | datetime | 是 | NULL | 软删除时间 | | `sDeletedBy` | varchar(50) | 是 | NULL | 软删除操作人 | diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..8f85caa --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,8 @@ +node_modules +dist +dist-ssr +*.local +.vite +.env.local +tsconfig.tsbuildinfo +*.tsbuildinfo diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..d9487ce --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,18 @@ + + + + + + XLY-ERP · 印刷制造管理平台 + + + + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..64c715f --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,3224 @@ +{ + "name": "xly-erp-frontend", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "xly-erp-frontend", + "version": "0.0.1", + "dependencies": { + "@ant-design/icons": "^5.5.1", + "@reduxjs/toolkit": "^2.2.7", + "antd": "^5.21.5", + "axios": "^1.7.7", + "dayjs": "^1.11.13", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-redux": "^9.1.2", + "react-router-dom": "^6.27.0" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "typescript": "^5.6.3", + "vite": "^5.4.10" + } + }, + "node_modules/@ant-design/colors": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz", + "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz", + "integrity": "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz", + "integrity": "sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.3.tgz", + "integrity": "sha512-bk/FJ09fLf+NLODMAFll6CfYrHPBioTedhW6lxDBuuWucJEqFUd4l/D/5JgIi3dina6sYahB8iuPAZTNz2pMxw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz", + "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.1.tgz", + "integrity": "sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/antd": { + "version": "5.29.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.29.3.tgz", + "integrity": "sha512-3DdbGCa9tWAJGcCJ6rzR8EJFsv2CtyEbkVabZE14pfgUHfCicWCj0/QzQVLDYg8CPfQk9BH7fHCoTXHTy7MP/A==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.2.1", + "@ant-design/cssinjs": "^1.23.0", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/fast-color": "^2.0.6", + "@ant-design/icons": "^5.6.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.26.0", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.1.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.3.0", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.34.0", + "rc-checkbox": "~3.5.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.3.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.1", + "rc-image": "~7.12.0", + "rc-input": "~1.8.0", + "rc-input-number": "~9.5.0", + "rc-mentions": "~2.20.0", + "rc-menu": "~9.16.1", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.4", + "rc-pagination": "~5.1.0", + "rc-picker": "~4.11.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.1", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.7.0", + "rc-select": "~14.16.8", + "rc-slider": "~11.1.9", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.54.0", + "rc-tabs": "~15.7.0", + "rc-textarea": "~1.10.2", + "rc-tooltip": "~6.4.0", + "rc-tree": "~5.13.1", + "rc-tree-select": "~5.27.0", + "rc-upload": "~4.11.0", + "rc-util": "^5.44.4", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", + "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/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/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.345", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.345.tgz", + "integrity": "sha512-F9JXQGiMrz6yVNPI2qOVPvB9HzjH5cGzhs8oJ6A28V5L/YnzN/0KsuiibqF+F1Fd9qxFzD1BUnYSd8JfULxTwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/immer": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/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.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/rc-cascader": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz", + "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz", + "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz", + "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.1.tgz", + "integrity": "sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz", + "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz", + "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz", + "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.8.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.20.0.tgz", + "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.8.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.10.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.1", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz", + "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.4.tgz", + "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.5.0.tgz", + "integrity": "sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz", + "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz", + "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz", + "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.1.tgz", + "integrity": "sha512-izj1Nw/Dw2Vb7EVr+D/E9lUTkBe+kKC+SAFSU9zqr7WV2W5Ktaa9Gc7cB2jTqgk8GROJayltaec+DBlYKc6d+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.8", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz", + "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.9.tgz", + "integrity": "sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.54.0", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.54.0.tgz", + "integrity": "sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.44.3", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.7.0.tgz", + "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.2.tgz", + "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.8.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz", + "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1", + "rc-util": "^5.44.3" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz", + "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz", + "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-upload": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.11.0.tgz", + "integrity": "sha512-ZUyT//2JAehfHzjWowqROcwYJKnZkIUGWaTE/VogVrepSl7AFNbQf4+zGfX4zl9Vrj/Jm8scLO0R6UlPDKK4wA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-virtual-list": { + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.19.2.tgz", + "integrity": "sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.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", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..81e9d2c --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,31 @@ +{ + "name": "xly-erp-frontend", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "tsc --noEmit", + "test": "echo 'no frontend unit tests'", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/icons": "^5.5.1", + "@reduxjs/toolkit": "^2.2.7", + "antd": "^5.21.5", + "axios": "^1.7.7", + "dayjs": "^1.11.13", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-redux": "^9.1.2", + "react-router-dom": "^6.27.0" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "typescript": "^5.6.3", + "vite": "^5.4.10" + } +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..e074196 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,19 @@ +import { ConfigProvider, App as AntApp } from "antd"; +import zhCN from "antd/locale/zh_CN"; +import { Provider } from "react-redux"; +import { RouterProvider } from "react-router-dom"; +import { store } from "@/store"; +import { router } from "@/router"; +import { antdTheme } from "@/styles/theme"; + +export default function App() { + return ( + + + + + + + + ); +} diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts new file mode 100644 index 0000000..96c9fa3 --- /dev/null +++ b/frontend/src/api/auth.ts @@ -0,0 +1,28 @@ +import { request } from "./client"; + +export interface UserBrief { + iIncrement: number; + sUserNo: string; + sUserName: string; + sUserType: string; + sLanguage: string; +} + +export interface LoginResponse { + accessToken: string; + refreshToken: string; + expiresIn: number; + user: UserBrief; +} + +export function login( + sUserName: string, + password: string, + version: string +): Promise { + return request({ + url: "/usr/auth/login", + method: "POST", + data: { sUserName, password, version }, + }); +} diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts new file mode 100644 index 0000000..6e22a54 --- /dev/null +++ b/frontend/src/api/client.ts @@ -0,0 +1,76 @@ +import axios, { AxiosError } from "axios"; +import { message } from "antd"; + +const TOKEN_KEY = "xly.access"; + +export function getToken(): string | null { + return sessionStorage.getItem(TOKEN_KEY); +} +export function setToken(t: string): void { + sessionStorage.setItem(TOKEN_KEY, t); +} +export function clearToken(): void { + sessionStorage.removeItem(TOKEN_KEY); +} + +export interface ApiEnvelope { + code: number; + msg: string; + data: T; +} + +export class ApiError extends Error { + code: number; + constructor(code: number, msg: string) { + super(msg); + this.code = code; + } +} + +export const http = axios.create({ + baseURL: "/api", + timeout: 15000, + headers: { "Content-Type": "application/json" }, +}); + +http.interceptors.request.use((config) => { + const tok = getToken(); + if (tok) { + config.headers = config.headers ?? {}; + (config.headers as Record).Authorization = `Bearer ${tok}`; + } + return config; +}); + +http.interceptors.response.use( + (response) => { + const env = response.data as ApiEnvelope; + if (env && typeof env.code === "number" && env.code !== 0) { + message.error(env.msg || "请求失败"); + throw new ApiError(env.code, env.msg || "请求失败"); + } + return response; + }, + (err: AxiosError) => { + if (err.response?.status === 401) { + clearToken(); + // Redirect to /login. Use location to avoid coupling axios with React Router. + if (window.location.pathname !== "/login") { + window.location.href = "/login"; + } + return Promise.reject(new ApiError(401, "未登录或会话过期")); + } + const env = err.response?.data; + const msg = env?.msg || err.message || "网络错误"; + message.error(msg); + return Promise.reject(new ApiError(env?.code ?? -1, msg)); + } +); + +// Convenience: call http and unwrap envelope to data. +export async function request( + config: Parameters[0] +): Promise { + const resp = await http.request>(config); + return resp.data.data; +} diff --git a/frontend/src/api/module.ts b/frontend/src/api/module.ts new file mode 100644 index 0000000..6cebb01 --- /dev/null +++ b/frontend/src/api/module.ts @@ -0,0 +1,45 @@ +import { request } from "./client"; + +export interface ModuleTreeVO { + iIncrement: number; + sModuleNameZh: string; + sDisplayType: string; + sManageDeptEn: string; + iParentId: number | null; + iSortOrder: number; + children: ModuleTreeVO[]; +} + +export interface ModuleDTO { + sDisplayType: string; + sProcedureName?: string; + sModuleType: string; + sManageDeptEn: string; + bShowPermission?: boolean; + sModuleNameZh: string; + iParentId?: number | null; + iSortOrder?: number; +} + +export function listModules(keyword?: string): Promise { + return request({ + url: "/mod/modules", + method: "GET", + params: keyword ? { keyword } : {}, + }); +} + +export function createModule(dto: ModuleDTO): Promise<{ iIncrement: number }> { + return request({ url: "/mod/modules", method: "POST", data: dto }); +} + +export function updateModule( + id: number, + dto: ModuleDTO +): Promise<{ iIncrement: number }> { + return request({ url: `/mod/modules/${id}`, method: "PUT", data: dto }); +} + +export function deleteModule(id: number): Promise { + return request({ url: `/mod/modules/${id}`, method: "DELETE" }); +} diff --git a/frontend/src/api/staff.ts b/frontend/src/api/staff.ts new file mode 100644 index 0000000..fd242ef --- /dev/null +++ b/frontend/src/api/staff.ts @@ -0,0 +1,16 @@ +import { request } from "./client"; + +export interface StaffSearchVO { + iIncrement: number; + sStaffNo: string; + sStaffName: string; + sDepartment: string | null; +} + +export function searchStaff(keyword?: string, limit = 20): Promise { + return request({ + url: "/usr/staffs", + method: "GET", + params: { keyword: keyword ?? "", limit }, + }); +} diff --git a/frontend/src/api/user.ts b/frontend/src/api/user.ts new file mode 100644 index 0000000..47a9a88 --- /dev/null +++ b/frontend/src/api/user.ts @@ -0,0 +1,78 @@ +import { request } from "./client"; + +export interface UserListVO { + iIncrement: number; + sUserName: string; + iStaffId: number | null; + staffName: string | null; + sUserNo: string; + department: string | null; + sUserType: string; + sLanguage: string; + bCanModifyDocs?: boolean; + permissionCategoryIds?: number[]; + bDeleted: boolean; + tLastLoginDate: string | null; + sCreatedBy: string | null; + tCreateDate: string; +} + +export interface PermissionCategoryVO { + iIncrement: number; + sCategoryCode: string; + sCategoryName: string; + iParentId: number | null; + iSortOrder: number; +} + +export interface UserListPage { + records: UserListVO[]; + total: number; + pageNum: number; + pageSize: number; +} + +export interface UserListParams { + field?: string; + match?: string; + value?: string; + pageNum?: number; + pageSize?: number; +} + +export interface UserDTO { + sUserNo: string; + sUserName: string; + iStaffId?: number | null; + sUserType: string; + sLanguage: string; + bCanModifyDocs?: boolean; + permissionCategoryIds?: number[]; +} + +export function listUsers(params: UserListParams = {}): Promise { + return request({ + url: "/usr/users", + method: "GET", + params, + }); +} + +export function getUser(id: number): Promise { + return request({ url: `/usr/users/${id}`, method: "GET" }); +} + +export function listPermissionCategories(): Promise { + return request({ + url: "/usr/permission-categories", + method: "GET", + }); +} + +export function createUser(dto: UserDTO): Promise<{ iIncrement: number; sUserNo: string }> { + return request({ url: "/usr/users", method: "POST", data: dto }); +} + +export function updateUser(id: number, dto: UserDTO): Promise<{ iIncrement: number }> { + return request({ url: `/usr/users/${id}`, method: "PUT", data: dto }); +} diff --git a/frontend/src/components/MegaNav.tsx b/frontend/src/components/MegaNav.tsx new file mode 100644 index 0000000..aa3c408 --- /dev/null +++ b/frontend/src/components/MegaNav.tsx @@ -0,0 +1,216 @@ +import { useState } from "react"; +import { + AppstoreOutlined, + HomeOutlined, + CloseOutlined, + FolderOutlined, + SettingOutlined, +} from "@ant-design/icons"; +import { MEGA_NAV, MEGA_COLUMNS } from "@/utils/data"; + +interface Props { + onClose: () => void; + onOpen: (screen: string, label: string) => void; +} + +export default function MegaNav({ onClose, onOpen }: Props) { + const [activeId, setActiveId] = useState( + MEGA_NAV.find((s) => s.active)?.id ?? "sys" + ); + const cols = MEGA_COLUMNS[activeId]; + + return ( +
+
+ + +
+ +
+ +
+
+ {MEGA_NAV.map((s) => { + const active = s.id === activeId; + return ( +
setActiveId(s.id)} + style={{ + display: "flex", + alignItems: "center", + gap: 8, + padding: "8px 14px", + cursor: "pointer", + fontSize: 12, + background: active ? "var(--accent)" : "transparent", + color: active ? "#fff" : "var(--text-on-dark)", + borderLeft: active ? "3px solid #fff" : "3px solid transparent", + }} + onMouseEnter={(e) => { + if (!active) e.currentTarget.style.background = "rgba(255,255,255,0.06)"; + }} + onMouseLeave={(e) => { + if (!active) e.currentTarget.style.background = "transparent"; + }} + > + {s.id === "sys" ? : } + {s.label} +
+ ); + })} +
+ +
+ {cols ? ( +
+ {cols.map((col) => ( +
+
+ {col.title} +
+
+ {col.items.map((it, i) => { + const clickable = !!it.screen; + return ( +
{ + onOpen(it.screen!, it.label); + onClose(); + } + : undefined + } + style={{ + fontSize: 12, + color: it.featured ? "var(--accent)" : "var(--text-on-dark-muted)", + cursor: clickable ? "pointer" : "default", + display: "inline-flex", + alignItems: "center", + gap: 4, + padding: "1px 0", + }} + onMouseEnter={(e) => { + if (clickable) e.currentTarget.style.color = "#fff"; + }} + onMouseLeave={(e) => { + if (clickable) + e.currentTarget.style.color = it.featured + ? "var(--accent)" + : "var(--text-on-dark-muted)"; + }} + > + {it.label} + {it.featured ? ( + + ) : null} +
+ ); + })} +
+
+ ))} +
+ ) : ( +
+ {MEGA_NAV.find((s) => s.id === activeId)?.label} · 模块开发中 +
+ )} +
+
+
+ ); +} diff --git a/frontend/src/components/Primitives.tsx b/frontend/src/components/Primitives.tsx new file mode 100644 index 0000000..32de319 --- /dev/null +++ b/frontend/src/components/Primitives.tsx @@ -0,0 +1,325 @@ +// Bespoke primitives that mirror prototype/src/primitives.jsx exactly so +// screens like UserDetail keep pixel-level fidelity to the prototype rather +// than picking up AntD's default chrome. + +import React from "react"; + +const fieldStyles = { + label: { + width: 88, + flex: "none" as const, + textAlign: "right" as const, + paddingRight: 8, + color: "var(--text)", + fontSize: 12, + lineHeight: "26px", + whiteSpace: "nowrap" as const, + overflow: "hidden" as const, + textOverflow: "ellipsis" as const, + }, + control: { + flex: 1, + minWidth: 0, + height: "var(--input-h)", + border: "1px solid var(--border-input)", + background: "var(--bg-input)", + padding: "0 6px", + fontSize: 12, + color: "var(--text)", + borderRadius: 0, + outline: "none", + fontFamily: "inherit", + }, + controlDisabled: { background: "var(--bg-disabled)", color: "var(--text-muted)" }, + controlReq: { background: "#d4e8f7" }, // light cyan tint matching prototype screenshots +}; + +interface FieldProps { + label: string; + required?: boolean; + children: React.ReactNode; + labelWidth?: number; +} + +export function Field({ label, required, children, labelWidth }: FieldProps) { + return ( +
+
+ {required ? * : null} + {label}: +
+ {children} +
+ ); +} + +interface InputProps { + value: string; + onChange?: (v: string) => void; + disabled?: boolean; + required?: boolean; + placeholder?: string; + mono?: boolean; +} + +export function PrimInput({ value, onChange, disabled, required, placeholder, mono }: InputProps) { + return ( + onChange(e.target.value) : undefined} + disabled={disabled} + placeholder={placeholder} + style={{ + ...fieldStyles.control, + ...(disabled ? fieldStyles.controlDisabled : {}), + ...(required && !disabled ? fieldStyles.controlReq : {}), + ...(mono + ? { fontFamily: '"JetBrains Mono", Menlo, Consolas, monospace', fontSize: 11 } + : {}), + }} + /> + ); +} + +interface SelectOption { + value: string; + label: string; +} + +interface SelectProps { + value: string; + onChange?: (v: string) => void; + disabled?: boolean; + required?: boolean; + options: (string | SelectOption)[]; + allowEmpty?: boolean; +} + +export function PrimSelect({ + value, + onChange, + disabled, + required, + options, + allowEmpty = true, +}: SelectProps) { + return ( +
+ +
+ ▼ +
+
+ ); +} + +interface CheckboxProps { + checked: boolean; + onChange?: (v: boolean) => void; + disabled?: boolean; + label?: string; + size?: number; +} + +export function PrimCheckbox({ checked, onChange, disabled, label, size = 13 }: CheckboxProps) { + return ( + + ); +} + +interface ToolbarBtnDarkProps { + children: React.ReactNode; + icon?: React.ReactNode; + onClick?: () => void; + disabled?: boolean; + danger?: boolean; +} + +export function ToolbarBtnDark({ + children, + icon, + onClick, + disabled, + danger, +}: ToolbarBtnDarkProps) { + const [hover, setHover] = React.useState(false); + return ( + + ); +} + +interface ToolbarBtnLightProps { + children: React.ReactNode; + icon?: React.ReactNode; + onClick?: () => void; + disabled?: boolean; + primary?: boolean; + danger?: boolean; + success?: boolean; +} + +export function ToolbarBtnLight({ + children, + icon, + onClick, + disabled, + primary, + danger, + success, +}: ToolbarBtnLightProps) { + const [hover, setHover] = React.useState(false); + let bg = "#fff", + border = "var(--border-input)", + color = "var(--text)"; + if (primary) { + bg = hover ? "#3a9ae8" : "#5cabe8"; + border = "#3a8ad6"; + color = "#fff"; + } else if (danger) { + bg = hover ? "#e85a5a" : "#f07070"; + border = "#d04141"; + color = "#fff"; + } else if (success) { + bg = hover ? "#38b777" : "#4cc488"; + border = "#27a567"; + color = "#fff"; + } else if (hover && !disabled) { + bg = "var(--accent-soft)"; + border = "var(--accent)"; + } + return ( + + ); +} diff --git a/frontend/src/components/PrivateRoute.tsx b/frontend/src/components/PrivateRoute.tsx new file mode 100644 index 0000000..af3e07d --- /dev/null +++ b/frontend/src/components/PrivateRoute.tsx @@ -0,0 +1,9 @@ +import { Navigate } from "react-router-dom"; +import { useAppSelector } from "@/store"; +import { ReactNode } from "react"; + +export default function PrivateRoute({ children }: { children: ReactNode }) { + const token = useAppSelector((s) => s.auth.token); + if (!token) return ; + return <>{children}; +} diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx new file mode 100644 index 0000000..5ee750e --- /dev/null +++ b/frontend/src/components/Sidebar.tsx @@ -0,0 +1,153 @@ +import { useState } from "react"; +import { + HomeOutlined, + FolderOutlined, + FileTextOutlined, + SettingOutlined, + DownOutlined, + RightOutlined, + SearchOutlined, +} from "@ant-design/icons"; +import { NAV_TREE, type NavNode } from "@/utils/data"; + +interface Props { + activeNodeId: string; + onNodeClick: (node: NavNode) => void; +} + +const iconFor = (icon?: string) => { + switch (icon) { + case "home": + return ; + case "doc": + return ; + case "settings": + return ; + default: + return ; + } +}; + +export default function Sidebar({ activeNodeId, onNodeClick }: Props) { + const [expanded, setExpanded] = useState>({ + kpi: true, + quote: true, + sys: true, + }); + const [query, setQuery] = useState(""); + + const toggle = (id: string) => setExpanded((s) => ({ ...s, [id]: !s[id] })); + + const renderNode = (node: NavNode, depth: number): React.ReactNode => { + const hasChildren = !!(node.children && node.children.length); + const isOpen = !!expanded[node.id]; + const active = node.id === activeNodeId; + + if (query && !node.label.toLowerCase().includes(query.toLowerCase()) && !hasChildren) { + return null; + } + + return ( +
+
{ + if (hasChildren) toggle(node.id); + onNodeClick(node); + }} + style={{ + display: "flex", + alignItems: "center", + gap: 6, + paddingLeft: 8 + depth * 14, + paddingRight: 8, + height: 26, + fontSize: 12, + cursor: "pointer", + color: active ? "var(--accent-strong)" : "var(--text)", + background: active ? "var(--selected)" : "transparent", + borderLeft: active ? "2px solid var(--accent)" : "2px solid transparent", + fontWeight: active ? 500 : 400, + }} + onMouseEnter={(e) => { + if (!active) e.currentTarget.style.background = "var(--bg-row-hover)"; + }} + onMouseLeave={(e) => { + if (!active) e.currentTarget.style.background = "transparent"; + }} + > + + {hasChildren ? ( + isOpen ? ( + + ) : ( + + ) + ) : null} + + + {iconFor(node.icon)} + + + {node.label} + + {node.badge && ( + + {node.badge} + + )} +
+ {hasChildren && isOpen && ( +
{node.children!.map((c) => renderNode(c, depth + 1))}
+ )} +
+ ); + }; + + return ( +
+
+ + setQuery(e.target.value)} + placeholder="搜索菜单" + style={{ + flex: 1, + height: 22, + border: "1px solid var(--border-input)", + background: "var(--bg-input)", + padding: "0 6px", + fontSize: 12, + outline: "none", + fontFamily: "inherit", + }} + /> +
+
{NAV_TREE.map((n) => renderNode(n, 0))}
+
+ ); +} diff --git a/frontend/src/components/StaffPicker.tsx b/frontend/src/components/StaffPicker.tsx new file mode 100644 index 0000000..1380ad0 --- /dev/null +++ b/frontend/src/components/StaffPicker.tsx @@ -0,0 +1,175 @@ +import { useEffect, useRef, useState } from "react"; +import { SearchOutlined } from "@ant-design/icons"; +import { searchStaff, type StaffSearchVO } from "@/api/staff"; + +interface Props { + value: string; + onChange: (name: string, staffId: number | null) => void; + disabled?: boolean; + required?: boolean; + placeholder?: string; +} + +const fieldControl: React.CSSProperties = { + flex: 1, + minWidth: 0, + height: "var(--input-h)", + border: "1px solid var(--border-input)", + background: "var(--bg-input)", + padding: "0 26px 0 6px", + fontSize: 12, + color: "var(--text)", + borderRadius: 0, + outline: "none", + fontFamily: "inherit", +}; + +export default function StaffPicker({ + value, + onChange, + disabled, + required, + placeholder, +}: Props) { + const [open, setOpen] = useState(false); + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(false); + const wrapRef = useRef(null); + const debounce = useRef(null); + + useEffect(() => { + function onDocClick(e: MouseEvent) { + if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) { + setOpen(false); + } + } + document.addEventListener("mousedown", onDocClick); + return () => document.removeEventListener("mousedown", onDocClick); + }, []); + + const fetchSuggestions = async (kw: string) => { + setLoading(true); + try { + const list = await searchStaff(kw, 20); + setItems(list); + } catch { + setItems([]); + } finally { + setLoading(false); + } + }; + + const onFocus = () => { + if (disabled) return; + setOpen(true); + void fetchSuggestions(value); + }; + + const onInputChange = (next: string) => { + // Typing clears any previously bound staff id; user must re-select + onChange(next, null); + setOpen(true); + if (debounce.current) window.clearTimeout(debounce.current); + debounce.current = window.setTimeout(() => fetchSuggestions(next), 200); + }; + + const select = (s: StaffSearchVO) => { + onChange(s.sStaffName, s.iIncrement); + setOpen(false); + }; + + return ( +
+ onInputChange(e.target.value)} + onFocus={onFocus} + disabled={disabled} + placeholder={placeholder} + style={{ + ...fieldControl, + ...(disabled ? { background: "var(--bg-disabled)", color: "var(--text-muted)" } : {}), + ...(required && !disabled ? { background: "#d4e8f7" } : {}), + }} + /> + + + + {open && !disabled && ( +
+ {loading && ( +
+ 加载中… +
+ )} + {!loading && items.length === 0 && ( +
+ 没有匹配的职员 +
+ )} + {!loading && + items.map((s) => ( +
{ + e.preventDefault(); + select(s); + }} + style={{ + padding: "6px 12px", + fontSize: 12, + cursor: "pointer", + display: "flex", + alignItems: "center", + gap: 8, + borderBottom: "1px solid #f0f2f5", + }} + onMouseEnter={(e) => { + e.currentTarget.style.background = "var(--bg-row-hover)"; + }} + onMouseLeave={(e) => { + e.currentTarget.style.background = "transparent"; + }} + > + {s.sStaffName} + + {s.sStaffNo} + + + + {s.sDepartment ?? ""} + +
+ ))} +
+ )} +
+ ); +} diff --git a/frontend/src/components/TabStrip.tsx b/frontend/src/components/TabStrip.tsx new file mode 100644 index 0000000..df58dae --- /dev/null +++ b/frontend/src/components/TabStrip.tsx @@ -0,0 +1,39 @@ +import { CloseOutlined } from "@ant-design/icons"; +import { useAppDispatch, useAppSelector } from "@/store"; +import { closeTab, setActiveTab } from "@/store/tabsSlice"; + +export default function TabStrip() { + const tabs = useAppSelector((s) => s.tabs.tabs); + const activeTabId = useAppSelector((s) => s.tabs.activeTabId); + const dispatch = useAppDispatch(); + + return ( +
+ {tabs.map((t) => { + const active = t.id === activeTabId; + return ( +
dispatch(setActiveTab(t.id))} + > + + {t.label} + + {t.closable !== false && ( + { + e.stopPropagation(); + dispatch(closeTab(t.id)); + }} + > + + + )} +
+ ); + })} +
+ ); +} diff --git a/frontend/src/components/TopBar.tsx b/frontend/src/components/TopBar.tsx new file mode 100644 index 0000000..7994134 --- /dev/null +++ b/frontend/src/components/TopBar.tsx @@ -0,0 +1,119 @@ +import { + AppstoreOutlined, + HomeOutlined, + SearchOutlined, + BellOutlined, + BankOutlined, + DownOutlined, +} from "@ant-design/icons"; +import { Dropdown } from "antd"; +import { useAppDispatch, useAppSelector } from "@/store"; +import { logout } from "@/store/authSlice"; +import { resetTabs, setActiveTab } from "@/store/tabsSlice"; +import { useNavigate } from "react-router-dom"; + +interface Props { + onOpenMegaNav: () => void; +} + +export default function TopBar({ onOpenMegaNav }: Props) { + const auth = useAppSelector((s) => s.auth); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + + const onLogout = () => { + dispatch(logout()); + dispatch(resetTabs()); + navigate("/login", { replace: true }); + }; + + const goHome = () => { + dispatch(setActiveTab("home")); + }; + + return ( +
+ + +
+ + + XLY-ERP + + + · {auth.companyName ?? ""} + +
+ + + + + +
+ ); +} + +function Brand() { + return ( + + + + + + ); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..f2c0a80 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./styles/tokens.css"; +import "./styles/global.css"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + +); diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx new file mode 100644 index 0000000..0c1b238 --- /dev/null +++ b/frontend/src/pages/Home.tsx @@ -0,0 +1,104 @@ +import { useAppDispatch, useAppSelector } from "@/store"; +import { openTab } from "@/store/tabsSlice"; + +const STATS = [ + { label: "待办事项", value: 12, color: "#3a8ee0" }, + { label: "今日报价", value: 5, color: "#27a567" }, + { label: "进行中订单", value: 28, color: "#d98e1f" }, + { label: "待审核", value: 3, color: "#d04141" }, +]; + +export default function Home() { + const auth = useAppSelector((s) => s.auth); + const dispatch = useAppDispatch(); + + return ( +
+
+
+ 欢迎回来,{auth.user?.sUserName ?? ""} +
+
+ XLY-ERP · {auth.companyName ?? ""} +
+
+ +
+ {STATS.map((s) => ( +
+
+ {s.label} +
+
{s.value}
+
+ ))} +
+ +
+
快捷入口
+
+ + dispatch(openTab({ id: "userlist", label: "用户列表", screen: "userlist" })) + } + /> + + dispatch(openTab({ id: "module", label: "系统模块配置", screen: "module" })) + } + /> +
+
+
+ ); +} + +function QuickLink({ label, onClick }: { label: string; onClick: () => void }) { + return ( + + ); +} diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx new file mode 100644 index 0000000..0111aab --- /dev/null +++ b/frontend/src/pages/Login.tsx @@ -0,0 +1,247 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { App as AntApp, Button } from "antd"; +import { UserOutlined, LockOutlined, DownOutlined } from "@ant-design/icons"; +import { login } from "@/api/auth"; +import { useAppDispatch } from "@/store"; +import { loginSucceeded } from "@/store/authSlice"; +import { COMPANIES } from "@/utils/data"; + +export default function Login() { + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + const { message } = AntApp.useApp(); + + const [user, setUser] = useState("admin"); + const [pass, setPass] = useState("666666"); + const [companyId, setCompanyId] = useState("std"); + const [companyOpen, setCompanyOpen] = useState(false); + const [submitting, setSubmitting] = useState(false); + + const company = COMPANIES.find((c) => c.id === companyId); + + const submit = async (e?: React.FormEvent) => { + e?.preventDefault(); + if (!user || !pass) return; + setSubmitting(true); + try { + const data = await login(user, pass, companyId); + dispatch( + loginSucceeded({ + token: data.accessToken, + user: data.user, + companyId, + companyName: company?.name ?? companyId, + }) + ); + message.success("登录成功"); + navigate("/home", { replace: true }); + } catch { + // message.error already shown by interceptor + } finally { + setSubmitting(false); + } + }; + + return ( +
+
+
+
+ + + + + + + +
+
+ XLY-ERP +
+
+ 印刷制造管理平台 +
+
+ +
+
+ + + + + setUser(e.target.value)} + placeholder="请输入用户名" + style={inputStyle} + /> +
+
+ + + + + setPass(e.target.value)} + placeholder="请输入密码" + style={inputStyle} + /> +
+
+ + {companyOpen && ( +
+ {COMPANIES.map((c, i) => { + const sel = c.id === companyId; + return ( +
{ + setCompanyId(c.id); + setCompanyOpen(false); + }} + style={{ + padding: "9px 12px", + color: "#fff", + fontSize: 13, + cursor: "pointer", + background: sel ? "var(--accent)" : "transparent", + borderTop: i === 0 ? "none" : "1px solid rgba(255,255,255,0.04)", + }} + > + {c.name} +
+ ); + })} +
+ )} +
+ +
+
+ +
+ XLY 软件 · 印刷制造管理平台 · 版权所有 © 2017–2026 +
+
+ ); +} + +const fieldWrap: React.CSSProperties = { + display: "flex", + alignItems: "center", + height: 38, + background: "rgba(255,255,255,0.95)", + border: "1px solid rgba(255,255,255,0.15)", + marginBottom: 10, +}; +const fieldIcon: React.CSSProperties = { + width: 36, + display: "inline-flex", + alignItems: "center", + justifyContent: "center", +}; +const fieldDivider: React.CSSProperties = { + width: 1, + height: 18, + background: "rgba(0,0,0,0.12)", +}; +const inputStyle: React.CSSProperties = { + flex: 1, + height: "100%", + border: "none", + background: "transparent", + paddingLeft: 10, + paddingRight: 10, + fontSize: 13, + color: "#1a2332", + outline: "none", + fontFamily: "inherit", +}; diff --git a/frontend/src/pages/Workspace.tsx b/frontend/src/pages/Workspace.tsx new file mode 100644 index 0000000..c55fa9f --- /dev/null +++ b/frontend/src/pages/Workspace.tsx @@ -0,0 +1,107 @@ +import { useState } from "react"; +import TopBar from "@/components/TopBar"; +import Sidebar from "@/components/Sidebar"; +import TabStrip from "@/components/TabStrip"; +import MegaNav from "@/components/MegaNav"; +import Home from "@/pages/Home"; +import UserList from "@/pages/usr/UserList"; +import UserDetail from "@/pages/usr/UserDetail"; +import ModuleConfig from "@/pages/mod/ModuleConfig"; +import { useAppDispatch, useAppSelector } from "@/store"; +import { openTab } from "@/store/tabsSlice"; +import type { NavNode } from "@/utils/data"; + +export default function Workspace() { + const tabs = useAppSelector((s) => s.tabs.tabs); + const activeTabId = useAppSelector((s) => s.tabs.activeTabId); + const auth = useAppSelector((s) => s.auth); + const dispatch = useAppDispatch(); + const [activeNodeId, setActiveNodeId] = useState("home"); + const [megaOpen, setMegaOpen] = useState(false); + + const handleNodeClick = (node: NavNode) => { + setActiveNodeId(node.id); + if (node.screen === "userlist") { + dispatch(openTab({ id: "userlist", label: "用户列表", screen: "userlist" })); + } else if (node.screen === "module") { + dispatch(openTab({ id: "module", label: "系统模块配置", screen: "module" })); + } else if (node.id === "home") { + dispatch(openTab({ id: "home", label: "主页", screen: "home", closable: false })); + } else if (!node.children?.length) { + dispatch( + openTab({ id: node.id, label: node.label, screen: "stub", meta: { stubLabel: node.label } }) + ); + } + }; + + const handleMegaOpen = (screen: string, label: string) => { + dispatch(openTab({ id: screen, label, screen })); + }; + + const activeTab = tabs.find((t) => t.id === activeTabId); + const showSidebar = activeTabId === "home"; + + return ( +
+ setMegaOpen(true)} /> + +
+ {showSidebar && ( +
+ +
+ )} +
+ {activeTab && } +
+
+
+ + 就绪 · 当前用户 {auth.user?.sUserName ?? ""} · {auth.companyName ?? ""} + + XLY-ERP v8.6.2 · © 2017–2026 XLY 软件股份 +
+ {megaOpen && setMegaOpen(false)} onOpen={handleMegaOpen} />} +
+ ); +} + +interface RouterProps { + screen: string; + meta?: Record; + label: string; +} + +function ScreenRouter({ screen, meta, label }: RouterProps) { + if (screen === "home") return ; + if (screen === "userlist") return ; + if (screen === "module") return ; + if (screen === "userdetail") { + const userId = meta?.userId as number | undefined; + const mode = (meta?.mode as "view" | "edit" | "new") ?? "view"; + return ; + } + return ( +
+ {(meta?.stubLabel as string) ?? label} · 模块开发中 +
+ ); +} diff --git a/frontend/src/pages/mod/ModuleConfig.tsx b/frontend/src/pages/mod/ModuleConfig.tsx new file mode 100644 index 0000000..d415eb2 --- /dev/null +++ b/frontend/src/pages/mod/ModuleConfig.tsx @@ -0,0 +1,401 @@ +import { useEffect, useMemo, useState } from "react"; +import { + Button, + Form, + Input, + InputNumber, + Select, + Tree, + Checkbox, + App as AntApp, + Popconfirm, +} from "antd"; +import { + PlusOutlined, + EditOutlined, + SaveOutlined, + CloseOutlined, + DeleteOutlined, + ReloadOutlined, +} from "@ant-design/icons"; +import type { DataNode } from "antd/es/tree"; +import { + listModules, + createModule, + updateModule, + deleteModule, + type ModuleTreeVO, + type ModuleDTO, +} from "@/api/module"; +import { MODULE_DISPLAY_TYPES } from "@/utils/data"; + +type Mode = "view" | "edit" | "new"; + +interface FormShape { + sDisplayType: string; + sModuleNameZh: string; + sManageDeptEn: string; + sModuleType: string; + sProcedureName: string; + iSortOrder: number; + iParentId: number | null; + bShowPermission: boolean; +} + +export default function ModuleConfig() { + const { message } = AntApp.useApp(); + const [form] = Form.useForm(); + const [tree, setTree] = useState([]); + const [loading, setLoading] = useState(false); + const [selectedId, setSelectedId] = useState(null); + const [mode, setMode] = useState("view"); + const [submitting, setSubmitting] = useState(false); + + const reload = async () => { + setLoading(true); + try { + const data = await listModules(); + setTree(data); + } catch { + // interceptor + } finally { + setLoading(false); + } + }; + + useEffect(() => { + void reload(); + }, []); + + const flat = useMemo(() => flatten(tree), [tree]); + const treeData = useMemo(() => toTreeData(tree), [tree]); + const selected = flat.find((m) => m.iIncrement === selectedId) ?? null; + + useEffect(() => { + if (selected && mode === "view") { + form.setFieldsValue({ + sDisplayType: selected.sDisplayType, + sModuleNameZh: selected.sModuleNameZh, + sManageDeptEn: selected.sManageDeptEn, + sModuleType: "", + sProcedureName: "", + iSortOrder: selected.iSortOrder, + iParentId: selected.iParentId ?? null, + bShowPermission: false, + }); + } + }, [selected, mode, form]); + + const startEdit = () => { + if (!selected) return; + setMode("edit"); + }; + + const startNew = () => { + form.setFieldsValue({ + sDisplayType: "前端业务", + sModuleNameZh: "", + sManageDeptEn: "", + sModuleType: "", + sProcedureName: "", + iSortOrder: 1, + iParentId: selectedId, + bShowPermission: false, + }); + setMode("new"); + }; + + const cancel = () => { + setMode("view"); + }; + + const save = async () => { + try { + const values = await form.validateFields(); + const dto: ModuleDTO = { + sDisplayType: values.sDisplayType, + sModuleNameZh: values.sModuleNameZh, + sManageDeptEn: values.sManageDeptEn, + sModuleType: values.sModuleType, + sProcedureName: values.sProcedureName, + iSortOrder: values.iSortOrder, + iParentId: values.iParentId, + bShowPermission: values.bShowPermission, + }; + setSubmitting(true); + if (mode === "new") { + const res = await createModule(dto); + message.success("新增成功"); + await reload(); + setSelectedId(res.iIncrement); + setMode("view"); + } else if (selectedId) { + await updateModule(selectedId, dto); + message.success("保存成功"); + await reload(); + setMode("view"); + } + } catch { + // validation or interceptor + } finally { + setSubmitting(false); + } + }; + + const onDelete = async () => { + if (!selectedId) return; + try { + await deleteModule(selectedId); + message.success("删除成功"); + setSelectedId(null); + await reload(); + } catch { + // interceptor + } + }; + + const disabled = mode === "view"; + + return ( +
+
+ + + + + + + + +
+ + 状态:{mode === "view" ? "只读" : mode === "new" ? "新增中" : "编辑中"} + +
+ +
+
+ { + if (keys.length) setSelectedId(Number(keys[0])); + }} + blockNode + defaultExpandAll + disabled={loading || mode !== "view"} + style={{ padding: 6, fontSize: 12 }} + /> + {tree.length === 0 && !loading && ( +
+ 暂无模块数据 +
+ )} +
+ +
+
+
+ + + + + + + + + + + + + + + + + setFilter(e.target.value)} + placeholder="过滤..." + style={{ + height: 20, + padding: "0 6px", + border: "1px solid var(--border-input)", + background: "#fff", + fontSize: 11, + width: 120, + fontFamily: "inherit", + outline: "none", + }} + /> + + + + + + {visibleCategories.map((c, i) => { + const isHover = hovered === c.sCategoryCode; + const checked = selected.has(c.iIncrement); + return ( + setHovered(c.sCategoryCode)} + onMouseLeave={() => setHovered(null)} + onClick={disabled ? undefined : () => setPermission(c.iIncrement, !checked)} + style={{ + background: + isHover && !disabled + ? "var(--bg-row-hover)" + : checked + ? "var(--accent-soft)" + : i % 2 === 0 + ? "#fff" + : "var(--bg-row-zebra)", + cursor: disabled ? "default" : "pointer", + height: 24, + }} + > + + setPermission(c.iIncrement, v)} + disabled={disabled} + /> + + + {c.sCategoryName} + + + ); + })} + + + ); +} + +interface ScopeTabProps { + tabKey: string; + enabled: boolean; + onToggle: (v: boolean) => void; + disabled?: boolean; +} + +function ScopeTab({ tabKey, enabled, onToggle, disabled }: ScopeTabProps) { + const cfg = SCOPE_ITEMS[tabKey]; + const [filter, setFilter] = useState(""); + if (!cfg) return null; + + const filtered = cfg.items.filter((it) => !filter || it.includes(filter)); + + return ( +
+
+ + setFilter(e.target.value)} + placeholder={`筛选${cfg.label}...`} + style={{ + height: 24, + padding: "0 8px", + border: "1px solid var(--border-input)", + background: "var(--bg-input)", + fontSize: 12, + width: 200, + fontFamily: "inherit", + outline: "none", + }} + /> + + + 共 {cfg.items.length} 个{cfg.label} + +
+
+ {filtered.map((it) => ( + + ))} +
+
+ ); +} diff --git a/frontend/src/pages/usr/UserList.tsx b/frontend/src/pages/usr/UserList.tsx new file mode 100644 index 0000000..868f81d --- /dev/null +++ b/frontend/src/pages/usr/UserList.tsx @@ -0,0 +1,238 @@ +import { useEffect, useState, useCallback } from "react"; +import { Button, Select, Input, Table, Checkbox, App as AntApp } from "antd"; +import { + ReloadOutlined, + PlusOutlined, + ExportOutlined, + SearchOutlined, +} from "@ant-design/icons"; +import type { ColumnsType } from "antd/es/table"; +import { listUsers, type UserListVO } from "@/api/user"; +import { USER_LIST_FIELDS, USER_LIST_MATCHES } from "@/utils/data"; +import { fmtDateTime } from "@/utils/format"; +import { useAppDispatch } from "@/store"; +import { openTab } from "@/store/tabsSlice"; + +type Scope = "all" | "active" | "disabled"; + +export default function UserList() { + const dispatch = useAppDispatch(); + const { message } = AntApp.useApp(); + + const [scope, setScope] = useState("all"); + const [field, setField] = useState("员工名"); + const [match, setMatch] = useState("包含"); + const [query, setQuery] = useState(""); + const [loading, setLoading] = useState(false); + const [rows, setRows] = useState([]); + const [total, setTotal] = useState(0); + const [selected, setSelected] = useState([]); + + const reload = useCallback( + async (override?: { field?: string; match?: string; value?: string }) => { + setLoading(true); + try { + const page = await listUsers({ + field: override?.field ?? field, + match: override?.match ?? match, + value: override?.value ?? query, + pageNum: 1, + pageSize: 100, + }); + setRows(page.records ?? []); + setTotal(page.total ?? 0); + } catch { + // interceptor already showed message + } finally { + setLoading(false); + } + }, + [field, match, query] + ); + + useEffect(() => { + void reload(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const filtered = rows.filter((r) => { + if (scope === "active") return !r.bDeleted; + if (scope === "disabled") return r.bDeleted; + return true; + }); + + const onCreate = () => { + dispatch( + openTab({ + id: "user-new", + label: "用户信息单据 · 新建", + screen: "userdetail", + meta: { mode: "new" }, + }) + ); + }; + + const onOpenUser = (u: UserListVO) => { + dispatch( + openTab({ + id: `user-${u.iIncrement}`, + label: `用户信息单据 · ${u.staffName ?? u.sUserName}`, + screen: "userdetail", + meta: { mode: "view", userId: u.iIncrement, snapshot: u }, + }) + ); + }; + + const columns: ColumnsType = [ + { + title: "序号", + dataIndex: "iIncrement", + width: 60, + render: (_, __, i) => {i + 1}, + }, + { title: "员工名", dataIndex: "staffName", width: 100, render: (v, r) => v ?? r.sUserName }, + { title: "员工号", dataIndex: "sUserNo", width: 100 }, + { title: "用户号", dataIndex: "sUserName", width: 100, render: (v) => {v} }, + { title: "部门", dataIndex: "department", width: 120, render: (v) => v ?? "—" }, + { title: "用户类型", dataIndex: "sUserType", width: 110 }, + { title: "语言", dataIndex: "sLanguage", width: 70 }, + { + title: "作废", + dataIndex: "bDeleted", + width: 60, + render: (v: boolean) => , + }, + { + title: "登录日期", + dataIndex: "tLastLoginDate", + width: 150, + render: (v) => {fmtDateTime(v)}, + }, + { title: "制单人", dataIndex: "sCreatedBy", width: 100 }, + { + title: "制单日期", + dataIndex: "tCreateDate", + width: 150, + render: (v) => {fmtDateTime(v)}, + }, + ]; + + return ( +
+
+ + + + + + setQuery(e.target.value)} + onPressEnter={() => reload()} + style={{ width: 180 }} + /> + + +
+ + {selected.length > 0 && ( + <> + 已选 {selected.length} 条 /{" "} + + )} + 共 {total} 条记录 + +
+ +
+ + rowKey="iIncrement" + columns={columns} + dataSource={filtered} + loading={loading} + size="small" + pagination={false} + rowSelection={{ + selectedRowKeys: selected, + onChange: (keys) => setSelected(keys as number[]), + }} + onRow={(r) => ({ + onDoubleClick: () => onOpenUser(r), + style: { cursor: "pointer", color: r.bDeleted ? "var(--text-faint)" : "var(--text)" }, + })} + /> +
+ +
+ + 当前显示:共 {filtered.length} 条单据 共 {total} 条记录 + + 双击行查看详情 +
+
+ ); +} diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx new file mode 100644 index 0000000..d5b6519 --- /dev/null +++ b/frontend/src/router/index.tsx @@ -0,0 +1,17 @@ +import { createBrowserRouter, Navigate } from "react-router-dom"; +import Login from "@/pages/Login"; +import Workspace from "@/pages/Workspace"; +import PrivateRoute from "@/components/PrivateRoute"; + +export const router = createBrowserRouter([ + { path: "/login", element: }, + { + path: "/*", + element: ( + + + + ), + }, + { path: "/", element: }, +]); diff --git a/frontend/src/store/authSlice.ts b/frontend/src/store/authSlice.ts new file mode 100644 index 0000000..5cb266b --- /dev/null +++ b/frontend/src/store/authSlice.ts @@ -0,0 +1,49 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import type { UserBrief } from "@/api/auth"; +import { getToken, setToken, clearToken } from "@/api/client"; + +interface AuthState { + token: string | null; + user: UserBrief | null; + companyId: string | null; // "std" / "ent" / "trial" + companyName: string | null; +} + +const initialState: AuthState = { + token: getToken(), + user: null, + companyId: null, + companyName: null, +}; + +const slice = createSlice({ + name: "auth", + initialState, + reducers: { + loginSucceeded( + state, + action: PayloadAction<{ + token: string; + user: UserBrief; + companyId: string; + companyName: string; + }> + ) { + state.token = action.payload.token; + state.user = action.payload.user; + state.companyId = action.payload.companyId; + state.companyName = action.payload.companyName; + setToken(action.payload.token); + }, + logout(state) { + state.token = null; + state.user = null; + state.companyId = null; + state.companyName = null; + clearToken(); + }, + }, +}); + +export const { loginSucceeded, logout } = slice.actions; +export default slice.reducer; diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts new file mode 100644 index 0000000..63813f3 --- /dev/null +++ b/frontend/src/store/index.ts @@ -0,0 +1,17 @@ +import { configureStore } from "@reduxjs/toolkit"; +import authReducer from "./authSlice"; +import tabsReducer from "./tabsSlice"; +import { useDispatch, useSelector, type TypedUseSelectorHook } from "react-redux"; + +export const store = configureStore({ + reducer: { + auth: authReducer, + tabs: tabsReducer, + }, +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; + +export const useAppDispatch: () => AppDispatch = useDispatch; +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/frontend/src/store/tabsSlice.ts b/frontend/src/store/tabsSlice.ts new file mode 100644 index 0000000..24e94e2 --- /dev/null +++ b/frontend/src/store/tabsSlice.ts @@ -0,0 +1,53 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; + +export interface OpenTab { + id: string; + label: string; + screen: string; + closable?: boolean; + meta?: Record; +} + +interface TabsState { + tabs: OpenTab[]; + activeTabId: string; +} + +const initialState: TabsState = { + tabs: [{ id: "home", label: "主页", screen: "home", closable: false }], + activeTabId: "home", +}; + +const slice = createSlice({ + name: "tabs", + initialState, + reducers: { + openTab(state, action: PayloadAction) { + const exists = state.tabs.find((t) => t.id === action.payload.id); + if (!exists) state.tabs.push(action.payload); + state.activeTabId = action.payload.id; + }, + closeTab(state, action: PayloadAction) { + const id = action.payload; + const idx = state.tabs.findIndex((t) => t.id === id); + if (idx < 0) return; + const tab = state.tabs[idx]; + if (tab.closable === false) return; + state.tabs = state.tabs.filter((t) => t.id !== id); + if (state.activeTabId === id) { + const fallback = state.tabs[Math.max(0, idx - 1)] || state.tabs[0]; + state.activeTabId = fallback?.id ?? "home"; + } + }, + setActiveTab(state, action: PayloadAction) { + state.activeTabId = action.payload; + }, + resetTabs(state) { + state.tabs = [{ id: "home", label: "主页", screen: "home", closable: false }]; + state.activeTabId = "home"; + }, + }, +}); + +export const { openTab, closeTab, setActiveTab, resetTabs } = slice.actions; +export default slice.reducer; diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css new file mode 100644 index 0000000..dd003ed --- /dev/null +++ b/frontend/src/styles/global.css @@ -0,0 +1,162 @@ +* { box-sizing: border-box; } +html, body, #root { height: 100%; margin: 0; } +body { + font-family: "Noto Sans SC", "PingFang SC", "Microsoft YaHei", -apple-system, "Segoe UI", sans-serif; + font-size: 13px; + color: var(--text); + background: var(--bg-app); + -webkit-font-smoothing: antialiased; + overflow: hidden; +} +.mono { font-family: "JetBrains Mono", Menlo, Consolas, monospace; } + +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-thumb { background: #c5cdd9; border-radius: 5px; border: 2px solid var(--bg-app); } +::-webkit-scrollbar-thumb:hover { background: #aab3c2; } +::-webkit-scrollbar-track { background: transparent; } + +/* AntD density overrides — match prototype's 26px row height */ +.ant-table-cell { padding: 4px 8px !important; font-size: 12px; } +.ant-table-thead > tr > th { + padding: 5px 8px !important; + background: var(--bg-row-zebra) !important; + font-weight: 500; + border-bottom: 1px solid var(--border) !important; +} +.ant-table-tbody > tr > td { border-bottom: 1px solid #f0f2f5 !important; } +.ant-table-tbody > tr.ant-table-row:hover > td { background: var(--bg-row-hover) !important; } +.ant-table-tbody > tr.ant-table-row-selected > td { background: var(--selected) !important; } + +.ant-form-item { margin-bottom: 6px !important; } +.ant-form-item-label > label { font-size: 12px !important; height: 26px !important; } +.ant-form-item .ant-input, +.ant-form-item .ant-select-selector, +.ant-form-item .ant-input-number { + font-size: 12px !important; +} +.ant-input, .ant-select-selector, .ant-input-number { + border-radius: 0 !important; +} +.ant-btn { border-radius: 0 !important; } + +/* Tab strip styling — matches prototype */ +.tab-strip { + display: flex; + align-items: stretch; + background: var(--bg-tab-strip); + border-bottom: 1px solid var(--border); + height: 30px; + flex: none; + overflow: hidden; +} +.tab-strip .tab-item { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 0 12px; + font-size: 12px; + cursor: pointer; + color: var(--text); + border-right: 1px solid var(--border); + background: transparent; + height: 100%; +} +.tab-strip .tab-item.active { + background: var(--bg-tab-active); + color: var(--accent-strong); + border-top: 2px solid var(--accent); + font-weight: 500; +} +.tab-strip .tab-item .close { + display: inline-flex; + width: 14px; height: 14px; + align-items: center; justify-content: center; + color: var(--text-faint); + font-size: 14px; + line-height: 1; +} +.tab-strip .tab-item .close:hover { color: var(--danger); } + +/* Light blue toolbar (#eaf1f7) used in UserList & ModuleConfig */ +.blue-toolbar { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; + row-gap: 6px; + padding: 6px 8px; + background: #eaf1f7; + border-bottom: 1px solid var(--border); + flex: none; +} + +/* Dark sub-toolbar used in UserDetail */ +.dark-toolbar { + display: flex; + align-items: center; + gap: 2px; + flex-wrap: wrap; + row-gap: 4px; + padding: 4px 8px; + background: var(--bg-toolbar-dark); + flex: none; + border-bottom: 1px solid #1a1f2a; + color: var(--text-on-dark); +} +.dark-toolbar .ant-btn { + background: transparent !important; + border: 1px solid rgba(255,255,255,0.12) !important; + color: var(--text-on-dark) !important; + height: 24px !important; + font-size: 12px !important; + padding: 0 8px !important; +} +.dark-toolbar .ant-btn:hover:not(:disabled) { + background: rgba(255,255,255,0.06) !important; + border-color: rgba(255,255,255,0.25) !important; + color: #fff !important; +} +.dark-toolbar .ant-btn:disabled { + color: var(--text-on-dark-muted) !important; + opacity: 0.5; +} + +/* Topbar — full dark navy (#262d3a) bar */ +.top-bar { + height: 36px; + flex: none; + background: var(--bg-topbar); + color: var(--text-on-dark); + display: flex; + align-items: center; + padding: 0 10px; + border-bottom: 1px solid #1a1f2a; +} +.top-bar .top-btn { + display: inline-flex; + align-items: center; + gap: 5px; + height: 28px; + padding: 0 10px; + background: transparent; + color: var(--text-on-dark); + border: none; + cursor: pointer; + font-size: 12px; + font-family: inherit; +} +.top-bar .top-btn:hover { background: rgba(255,255,255,0.06); } + +/* Status bar */ +.status-bar { + height: 22px; + flex: none; + background: #fff; + border-top: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 12px; + font-size: 11px; + color: var(--text-muted); +} diff --git a/frontend/src/styles/theme.ts b/frontend/src/styles/theme.ts new file mode 100644 index 0000000..bd9453c --- /dev/null +++ b/frontend/src/styles/theme.ts @@ -0,0 +1,55 @@ +import type { ThemeConfig } from "antd"; + +export const antdTheme: ThemeConfig = { + token: { + colorPrimary: "#3a8ee0", + colorPrimaryHover: "#2776c6", + colorBgLayout: "#f3f5f8", + colorBorder: "#dce1e8", + colorText: "#2a3142", + colorTextSecondary: "#6b7280", + colorError: "#d04141", + colorWarning: "#d98e1f", + colorSuccess: "#27a567", + borderRadius: 0, + controlHeight: 26, + controlHeightSM: 22, + fontSize: 13, + fontFamily: + '"Noto Sans SC", "PingFang SC", "Microsoft YaHei", -apple-system, "Segoe UI", sans-serif', + }, + components: { + Table: { + cellPaddingBlock: 4, + cellPaddingInline: 8, + headerBg: "#fafbfc", + headerColor: "#2a3142", + rowHoverBg: "#eaf4fc", + rowSelectedBg: "#d4e8f7", + rowSelectedHoverBg: "#d4e8f7", + borderColor: "#dce1e8", + headerBorderRadius: 0, + }, + Button: { + controlHeight: 26, + paddingContentHorizontal: 10, + }, + Input: { + controlHeight: 26, + }, + Select: { + controlHeight: 26, + }, + Form: { + itemMarginBottom: 6, + verticalLabelPadding: "0 0 2px", + labelFontSize: 12, + }, + Tabs: { + itemColor: "#2a3142", + itemSelectedColor: "#2776c6", + itemHoverColor: "#3a8ee0", + inkBarColor: "#3a8ee0", + }, + }, +}; diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css new file mode 100644 index 0000000..5c3da44 --- /dev/null +++ b/frontend/src/styles/tokens.css @@ -0,0 +1,32 @@ +:root { + --accent: #3a8ee0; + --accent-strong: #2776c6; + --accent-soft: #eaf4fc; + --selected: #d4e8f7; + --bg-app: #f3f5f8; + --bg-card: #ffffff; + --bg-input: #fafbfc; + --bg-input-focus: #ffffff; + --bg-topbar: #262d3a; + --bg-toolbar-dark: #2c3340; + --bg-tab-strip: #e8ecf2; + --bg-tab-active: #ffffff; + --bg-row-zebra: #fafbfc; + --bg-row-hover: #eaf4fc; + --bg-disabled: #eef0f3; + --border: #dce1e8; + --border-strong: #c2cad6; + --border-input: #d6dce4; + --text: #2a3142; + --text-muted: #6b7280; + --text-faint: #9aa3b2; + --text-on-dark: #e9ecf2; + --text-on-dark-muted: #98a1b3; + --required: #e74c3c; + --success: #27a567; + --warning: #d98e1f; + --danger: #d04141; + --row-h: 26px; + --tb-h: 28px; + --input-h: 26px; +} diff --git a/frontend/src/utils/data.ts b/frontend/src/utils/data.ts new file mode 100644 index 0000000..62de063 --- /dev/null +++ b/frontend/src/utils/data.ts @@ -0,0 +1,251 @@ +// Static reference data — sidebar tree, mega-nav, dropdowns. Not backed by backend. +// Copied from prototype/src/data.jsx. USER_TYPES and LANGUAGES trimmed to match +// backend enum constraints (UserServiceImpl.USER_TYPES / LANGUAGES). + +export interface NavNode { + id: string; + label: string; + icon?: string; + leaf?: boolean; + badge?: string; + children?: NavNode[]; + screen?: string; +} + +export const COMPANIES = [ + { id: "std", name: "标准版 (Standard Edition) / 8s" }, + { id: "ent", name: "企业版 (Enterprise Edition) / 8s" }, + { id: "trial", name: "试用版 (Trial Edition) / 30d" }, +]; + +// Backend USER_TYPES enum: 普通用户 | 超级管理员 +export const USER_TYPES = ["超级管理员", "普通用户"]; + +// Visual-only — permission grid in UserDetail. Backend persistence (by IDs) +// not yet wired; toggling these does not round-trip. +export const PERMISSION_GROUPS = [ + "默认显示(必选)", + "禁止查看价格", + "客服跟单", + "报价组员工", + "物控部员工", + "供应链 PMC", + "允许查看订单价格", + "储运部员工", + "外部供应商", + "品质部员工", + "技术中心员工", + "机修组员工", + "生产部计划员工", + "外发组员工", + "模烫车间", + "装订车间", + "粘接工车间", + "品质部管理", + "精品车间", + "人事组", + "统计组", + "机修主管", + "样品开发部员工", + "设计开发", + "总经办", + "财务部", + "销售员", + "采购员", + "仓库管理员", +]; + +// Visual-only — scope tabs in UserDetail (客户/供应商/人员/工序/司机). +export const SCOPE_ITEMS: Record = { + customer: { + label: "客户", + items: [ + "上海印行包装", + "锐尚文创", + "京华彩印", + "广印纸品", + "万象图文", + "联合包装", + "鼎盛印刷", + "九洲胶印", + ], + }, + supplier: { + label: "供应商", + items: ["华东油墨", "正信纸业", "宝洁化工", "日新油墨", "三鼎纸业", "鸿丰胶辊", "永利印材"], + }, + staff: { + label: "人员", + items: ["管广飞", "李斌", "孟威", "王宽明", "潘强", "杨柳"], + }, + process: { + label: "工序", + items: ["印前", "印刷", "覆膜", "模切", "装订", "胶装", "丝网", "烫金", "包装"], + }, + driver: { + label: "司机", + items: ["陈师傅", "李师傅", "王师傅", "钱师傅", "赵师傅"], + }, +}; + +// Backend LANGUAGES enum: zh | en | zh-TW. Display strings for the form. +export const LANGUAGE_OPTIONS: { value: string; label: string }[] = [ + { value: "zh", label: "中文" }, + { value: "en", label: "英文" }, + { value: "zh-TW", label: "繁体" }, +]; + +// Backend list-API field names (Chinese — see UserServiceImpl.FIELD_MAP) +export const USER_LIST_FIELDS: { value: string; label: string }[] = [ + { value: "员工名", label: "员工名" }, + { value: "用户名", label: "用户名" }, + { value: "用户号", label: "用户号" }, + { value: "部门", label: "部门" }, +]; +export const USER_LIST_MATCHES: { value: string; label: string }[] = [ + { value: "包含", label: "包含" }, + { value: "不包含", label: "不包含" }, + { value: "等于", label: "等于" }, +]; + +export const NAV_TREE: NavNode[] = [ + { id: "home", label: "首页", icon: "home", leaf: true }, + { + id: "kpi", + label: "KPI 流程作业单", + icon: "doc", + children: [ + { + id: "quote", + label: "估价管理流程", + children: [ + { id: "quote-01", label: "01/04 【新增】新报价单", leaf: true, badge: "估价" }, + { id: "quote-02", label: "02/04 审核报价单->客户确认...", leaf: true }, + { id: "quote-03", label: "03/04 客户确认->二次确认", leaf: true }, + { id: "quote-04", label: "04/04 报价单->销售订单", leaf: true }, + ], + }, + { id: "order", label: "订单生产流程" }, + { id: "panel", label: "自动拼版流程" }, + { id: "ship", label: "销售送货流程" }, + { id: "purch", label: "物料采购流程" }, + ], + }, + { id: "crm", label: "CRM 管理", icon: "folder" }, + { id: "plm", label: "PLM 管理", icon: "folder" }, + { id: "prod", label: "产品管理", icon: "folder" }, + { id: "sales", label: "销售管理", icon: "folder" }, + { id: "mfg", label: "生产管理", icon: "folder" }, + { + id: "sys", + label: "系统管理", + icon: "settings", + children: [ + { id: "roles", label: "角色管理", leaf: true }, + { id: "menucfg", label: "菜单配置", leaf: true }, + { id: "log", label: "操作日志", leaf: true }, + ], + }, +]; + +export const MEGA_NAV = [ + { id: "sales-mgmt", label: "销售管理" }, + { id: "dcs", label: "DCS 系统" }, + { id: "prod-mgmt", label: "产品管理" }, + { id: "prod-ops", label: "生产运营" }, + { id: "prod-exec", label: "生产执行" }, + { id: "mold", label: "模具管理" }, + { id: "purch", label: "采购管理" }, + { id: "matwh", label: "材料库存" }, + { id: "fgwh", label: "成品库存" }, + { id: "outsrc", label: "外协管理" }, + { id: "logistics", label: "物流管理" }, + { id: "qc", label: "质量管理" }, + { id: "fin", label: "财务管理" }, + { id: "cost-pro", label: "成本管理(专)" }, + { id: "cost", label: "成本管理" }, + { id: "equip", label: "设备管理" }, + { id: "hr", label: "人事行政" }, + { id: "oa", label: "OA 系统" }, + { id: "base", label: "基础设置" }, + { id: "sys", label: "系统设置", active: true }, +]; + +export const MEGA_COLUMNS: Record< + string, + { title: string; items: { label: string; screen?: string; featured?: boolean }[] }[] +> = { + sys: [ + { + title: "期初设置", + items: [ + { label: "客户期初" }, + { label: "供应商期初" }, + { label: "材料期初" }, + { label: "产品期初" }, + { label: "数据导入" }, + { label: "离线导出下载" }, + ], + }, + { + title: "用户管理", + items: [ + { label: "用户列表", screen: "userlist", featured: true }, + { label: "系统权限" }, + { label: "系统权限稽查表" }, + { label: "权限组" }, + ], + }, + { + title: "系统参数", + items: [ + { label: "系统参数" }, + { label: "财务结账" }, + { label: "系统常量配置" }, + ], + }, + { + title: "计算方案", + items: [{ label: "方案列表" }, { label: "计算参数" }], + }, + { + title: "日志", + items: [ + { label: "个性化模块" }, + { label: "操作日志" }, + { label: "异常清除KPI任务表" }, + { label: "MYSQL监听器" }, + ], + }, + { + title: "开发平台", + items: [ + { label: "自定义开发范例" }, + { label: "系统功能模块设置" }, + { label: "EBC流程清单" }, + { label: "功能模块界面设置" }, + { label: "增删改存业务处理" }, + ], + }, + { + title: "API对接管理", + items: [ + { label: "调用第三方接口(TOKEN配置)" }, + { label: "调用第三方接口(接口定义)" }, + { label: "被第三方调用(生成token)" }, + { label: "数据同步" }, + { label: "被第三方调用(API定义)" }, + ], + }, + { + title: "系统模块", + items: [ + { label: "系统模块配置", screen: "module", featured: true }, + { label: "菜单配置" }, + { label: "模块字段配置" }, + ], + }, + ], +}; + +export const MODULE_DISPLAY_TYPES = ["手机端", "前端业务", "系统配置", "接口"]; diff --git a/frontend/src/utils/format.ts b/frontend/src/utils/format.ts new file mode 100644 index 0000000..b49553e --- /dev/null +++ b/frontend/src/utils/format.ts @@ -0,0 +1,13 @@ +import dayjs from "dayjs"; + +export function fmtDateTime(v: string | null | undefined): string { + if (!v) return ""; + const d = dayjs(v); + return d.isValid() ? d.format("YYYY-MM-DD HH:mm:ss") : String(v); +} + +export function fmtDate(v: string | null | undefined): string { + if (!v) return ""; + const d = dayjs(v); + return d.isValid() ? d.format("YYYY-MM-DD") : String(v); +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..dd5bb62 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": false, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..50162f9 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import path from "node:path"; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, + server: { + port: 5173, + proxy: { + "/api": { + target: "http://localhost:8080", + changeOrigin: true, + }, + }, + }, +}); diff --git a/prototype/XLY-ERP.html b/prototype/XLY-ERP.html new file mode 100644 index 0000000..0a90927 --- /dev/null +++ b/prototype/XLY-ERP.html @@ -0,0 +1,86 @@ + + + + +XLY-ERP · 印刷制造管理平台 + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + diff --git a/prototype/src/app.jsx b/prototype/src/app.jsx new file mode 100644 index 0000000..c2b299f --- /dev/null +++ b/prototype/src/app.jsx @@ -0,0 +1,9 @@ +// Top-level app: login → workspace. + +const App = () => { + const [session, setSession] = React.useState(null); + if (!session) return ; + return setSession(null)} />; +}; + +ReactDOM.createRoot(document.getElementById("root")).render(); diff --git a/prototype/src/data.jsx b/prototype/src/data.jsx new file mode 100644 index 0000000..826450d --- /dev/null +++ b/prototype/src/data.jsx @@ -0,0 +1,193 @@ +// Seed data — plausible printing-industry ERP tenant. +// Original company name; not derived from the reference product. + +const COMPANIES = [ + { id: "std", name: "标准版 (Standard Edition) / 8s" }, + { id: "ent", name: "企业版 (Enterprise Edition) / 8s" }, + { id: "trial", name: "试用版 (Trial Edition) / 30d" }, +]; + +// Sidebar tree — printing-industry processes +const NAV_TREE = [ + { id: "home", label: "首页", icon: "home", leaf: true }, + { + id: "kpi", label: "KPI 流程作业单", icon: "doc", + children: [ + { + id: "quote", label: "估价管理流程", + children: [ + { id: "quote-01", label: "01/04 【新增】新报价单", leaf: true, badge: "估价" }, + { id: "quote-02", label: "02/04 审核报价单->客户确认...", leaf: true }, + { id: "quote-03", label: "03/04 客户确认->二次确认", leaf: true }, + { id: "quote-04", label: "04/04 报价单->销售订单", leaf: true }, + { id: "quote-05", label: "04/04 报价单->转拼版单", leaf: true }, + { id: "quote-06", label: "04/07 主管审核报价单", leaf: true }, + { id: "quote-07", label: "05/07 业务确认报价单", leaf: true }, + ], + }, + { id: "order", label: "订单生产流程" }, + { id: "panel", label: "自动拼版流程" }, + { id: "ship", label: "销售送货流程" }, + { id: "purch", label: "物料采购流程" }, + { id: "issue", label: "物料领用流程" }, + { id: "outproc", label: "发外加工流程" }, + { id: "qc", label: "质量管理流程" }, + { id: "fin", label: "财务收付款流程" }, + ], + }, + { id: "crm", label: "CRM 管理", icon: "folder" }, + { id: "plm", label: "PLM 管理", icon: "folder" }, + { id: "prod", label: "产品管理", icon: "folder" }, + { id: "sales", label: "销售管理", icon: "folder" }, + { id: "mfg", label: "生产管理", icon: "folder" }, + { id: "exec", label: "生产执行", icon: "folder" }, + { id: "mold", label: "模具管理", icon: "folder" }, + { + id: "sys", label: "系统管理", icon: "settings", + children: [ + { id: "roles", label: "角色管理", leaf: true }, + { id: "menucfg", label: "菜单配置", leaf: true }, + { id: "log", label: "操作日志", leaf: true }, + ], + }, +]; + +// Permission-group rows (left column of permission tab) +const PERMISSION_GROUPS = [ + "权限分类", "默认显示(必选)", "禁止查看价格", "客服报单", + "报价组员工", "物控组员工", "供应链 PMC", "允许查看订单价格", + "储运员工", "外接供应商", "品质组员工", "技术中心员工", + "机修组员工", "生产排计划员工", "外发组员工", + "模切车间", "装订车间", "粘接工车间", "品质部管理", "精品车间", + "人事组", "统计组", "机修主管", "样品开发员工", "设计开发", "总经办", + "财务部", "销售员", "采购员", "仓库管理员", +]; + +const DEPARTMENTS = [ + "工艺技术", "印刷车间", "机修", "机务部", "财务部", + "装订车间", "总经办公室", "总务部", "供应链", "质量管理部", + "模切车间", "计划组", "样品开发", "设计部", "仓库", +]; + +const USER_TYPES = ["超级管理员", "高级管理员", "普通用户", "外部用户", "只读用户"]; +const LANGUAGES = ["中文", "英文", "繁体"]; + +// Plausible Chinese-name pinyin pairs +const NAME_POOL = [ + ["管广飞", "ggf"], ["李斌", "lib"], ["系统管理员", "admin"], ["朱财喜", "zhucx"], + ["林杰华", "ljh"], ["汪鑫", "wx"], ["钱昉", "qianb"], ["张冠飞", "zgf"], + ["孟威", "mengw"], ["杭仁萍", "hangrp"], ["王月", "wy"], ["王宽明", "wkm"], + ["潘强", "pq"], ["耿广东", "ggd"], ["余涛", "yt"], ["梁赵军", "lzj"], + ["曹佳怡", "cjy"], ["陈思琪", "csq"], ["张红英", "zhy"], ["吕欣彦", "lxy"], + ["陈雪婷", "cxt"], ["路鑫", "luxin"], ["陆鑫·储运部", "ZY0006"], + ["朱晓兵", "zhuxb"], ["孟丽花", "menglh"], ["彭敏", "pengm"], ["顾鹏", "gp"], + ["田雨", "ty"], ["黄文豪", "hwh"], ["邓佳", "dj"], ["孙浩然", "shr"], + ["徐瑞", "xr"], ["许云", "xy"], ["何晨曦", "hcx"], ["林婉君", "lwj"], + ["杨柳", "yl"], ["蒋婷", "jt"], +]; + +const seededRandom = (seed) => { + let s = seed; + return () => { s = (s * 9301 + 49297) % 233280; return s / 233280; }; +}; + +const pad = (n) => String(n).padStart(2, "0"); +const dt = (y, m, d, hh, mm, ss) => + `${y}-${pad(m)}-${pad(d)} ${pad(hh)}:${pad(mm)}:${pad(ss)}`; + +const buildUsers = () => { + const rand = seededRandom(7); + return NAME_POOL.map((nm, i) => { + const r = rand(); + return { + id: i + 1, + seq: i + 1, + employee: nm[0], + empNo: nm[1], + account: nm[1], + department: DEPARTMENTS[Math.floor(rand() * DEPARTMENTS.length)], + type: USER_TYPES[Math.floor(rand() * USER_TYPES.length)], + language: r < 0.15 ? "英文" : "中文", + disabled: r < 0.08, + lastLogin: dt(2026, 1 + Math.floor(rand() * 4), 1 + Math.floor(rand() * 27), + 8 + Math.floor(rand() * 10), Math.floor(rand() * 60), Math.floor(rand() * 60)), + createdBy: ["超级管理员", "机仁萍", "李丹", "YFZ", "LJH", "孟琰"][Math.floor(rand() * 6)], + createdAt: dt(2023 + Math.floor(rand() * 3), 1 + Math.floor(rand() * 12), + 1 + Math.floor(rand() * 27), 8 + Math.floor(rand() * 10), + Math.floor(rand() * 60), Math.floor(rand() * 60)), + // Default: a small subset of permission groups checked + permissions: PERMISSION_GROUPS.reduce((acc, g) => { + acc[g] = rand() < 0.18; return acc; + }, {}), + tabPerms: { customer: rand() < 0.4, supplier: rand() < 0.3, staff: rand() < 0.3, process: rand() < 0.3, driver: rand() < 0.2 }, + }; + }); +}; + +// Mega-nav grid: top-level sections (left rail) → category columns → leaf items +const MEGA_NAV = [ + { id: "sales-mgmt", icon: "folder", label: "销售管理" }, + { id: "dcs", icon: "folder", label: "DCS 系统" }, + { id: "prod-mgmt", icon: "folder", label: "产品管理" }, + { id: "prod-ops", icon: "folder", label: "生产运营" }, + { id: "prod-exec", icon: "folder", label: "生产执行" }, + { id: "mold", icon: "folder", label: "模具管理" }, + { id: "purch", icon: "folder", label: "采购管理" }, + { id: "matwh", icon: "folder", label: "材料库存" }, + { id: "fgwh", icon: "folder", label: "成品库存" }, + { id: "outsrc", icon: "folder", label: "外协管理" }, + { id: "logistics", icon: "folder", label: "物流管理" }, + { id: "qc", icon: "folder", label: "质量管理" }, + { id: "fin", icon: "folder", label: "财务管理" }, + { id: "cost-pro", icon: "folder", label: "成本管理(专)" }, + { id: "cost", icon: "folder", label: "成本管理" }, + { id: "equip", icon: "folder", label: "设备管理" }, + { id: "hr", icon: "folder", label: "人事行政" }, + { id: "oa", icon: "folder", label: "OA 系统" }, + { id: "base", icon: "folder", label: "基础设置" }, + { id: "sys", icon: "settings", label: "系统设置", active: true }, +]; + +const MEGA_COLUMNS = { + "sys": [ + { title: "期初设置", items: [ + { label: "客户期初" }, { label: "供应商期初" }, { label: "材料期初" }, { label: "产品期初" }, { label: "数据导入" }, { label: "离线导出下载" }, + ]}, + { title: "用户管理", items: [ + { label: "用户列表", screen: "userlist", featured: true }, + { label: "系统权限" }, { label: "系统权限程查表" }, { label: "权限组" }, + ]}, + { title: "系统参数", items: [ + { label: "系统参数" }, { label: "财务结准" }, { label: "系统常量配置" }, + ]}, + { title: "计算方案", items: [ + { label: "方案列表" }, { label: "计算参数" }, + ]}, + { title: "日志", items: [ + { label: "个性化模块" }, { label: "操作日志" }, { label: "异常清除KPI任务表" }, { label: "MYSQL监听器" }, + ]}, + { title: "开发平台", items: [ + { label: "自定义开发范例" }, { label: "系统功能模块设置" }, { label: "ERC流程清单" }, { label: "功能模块界面设置" }, { label: "模拟收付款业务处理" }, + ]}, + { title: "API 对接管理", items: [ + { label: "调用第三方接口(TOKEN配置)" }, { label: "调用第三方接口(接口定义)" }, { label: "被第三方调用(生成token)" }, { label: "数据同步" }, { label: "被第三方调用(API定义)" }, + ]}, + { title: "系统模块", items: [ + { label: "系统模块配置", screen: "module", featured: true }, + { label: "菜单配置" }, { label: "模块字段配置" }, + ]}, + ], + // Fallback for other sections — show a placeholder column +}; + +window.XLY = { + COMPANIES, + NAV_TREE, + MEGA_NAV, + MEGA_COLUMNS, + PERMISSION_GROUPS, + DEPARTMENTS, + USER_TYPES, + LANGUAGES, + buildUsers, +}; diff --git a/prototype/src/icons.jsx b/prototype/src/icons.jsx new file mode 100644 index 0000000..cbf66f5 --- /dev/null +++ b/prototype/src/icons.jsx @@ -0,0 +1,66 @@ +// Tiny inline SVG icons. All sized via currentColor. +const Ic = {}; + +const make = (name, paths, vb = "0 0 16 16") => { + Ic[name] = ({ size = 14, color, style, ...rest }) => ( + + {paths} + + ); +}; + +make("plus", <>); +make("edit", <>); +make("trash", <>); +make("save", <>); +make("cancel", <>); +make("refresh", <>); +make("export", <>); +make("import", <>); +make("search", <>); +make("close", <>); +make("chevronDown", <>); +make("chevronRight", <>); +make("chevronLeft", <>); +make("triangle", <>); +make("triangleR", <>); +make("user", <>); +make("lock", <>); +make("building", <>); +make("home", <>); +make("menu", <>); +make("bell", <>); +make("settings", <>); +make("function", <>); +make("key", <>); +make("redo", <>); +make("undo", <>); +make("clipboard", <>); +make("doc", <>); +make("folder", <>); +make("expand", <>); +make("dot", <>); +make("filter", <>); +make("sortAsc", <>); + +// Brand mark — overlapping registration squares (original geometric mark) +Ic.Brand = ({ size = 28, accent = "#3a8ee0" }) => ( + + + + + +); + +window.Ic = Ic; diff --git a/prototype/src/login.jsx b/prototype/src/login.jsx new file mode 100644 index 0000000..6ea35a1 --- /dev/null +++ b/prototype/src/login.jsx @@ -0,0 +1,163 @@ +// Login screen. Original logo (geometric registration-mark), original product name. + +const Login = ({ onLogin }) => { + const [user, setUser] = React.useState("admin"); + const [pass, setPass] = React.useState("••••••••"); + const [company, setCompany] = React.useState("std"); + const [companyOpen, setCompanyOpen] = React.useState(false); + const [submitting, setSubmitting] = React.useState(false); + + const submit = (e) => { + e && e.preventDefault(); + if (!user || !pass) return; + setSubmitting(true); + setTimeout(() => onLogin({ user, company: XLY.COMPANIES.find(c => c.id === company)?.name }), 480); + }; + + return ( +
+
+ {/* Logo + wordmark */} +
+
+ + {/* Three overlapping registration squares — print-press metaphor */} + + + + {/* registration cross */} + + + +
+
+ XLY-ERP +
+
+ 印刷制造管理平台 +
+
+ + + {/* User */} +
+ + + setUser(e.target.value)} + placeholder="请输入用户名" + style={loginStyles.input} + /> +
+ {/* Password */} +
+ + + setPass(e.target.value)} + placeholder="请输入密码" + style={loginStyles.input} + /> +
+ {/* Company picker */} +
+ + {companyOpen ? ( +
+ {XLY.COMPANIES.map((c, i) => { + const sel = c.id === company; + return ( +
{ setCompany(c.id); setCompanyOpen(false); }} + style={{ + padding: "9px 12px", color: "#fff", fontSize: 13, + cursor: "pointer", + background: sel ? "var(--accent)" : "transparent", + borderTop: i === 0 ? "none" : "1px solid rgba(255,255,255,0.04)", + }} + onMouseEnter={(e) => { if (!sel) e.currentTarget.style.background = "rgba(255,255,255,0.06)"; }} + onMouseLeave={(e) => { if (!sel) e.currentTarget.style.background = "transparent"; }} + > + {c.name} +
+ ); + })} +
+ ) : null} +
+ {/* Submit */} + + +
+ + {/* Footer */} +
+ XLY 软件 · 印刷制造管理平台 · 版权所有 © 2017–2026 +
+
+ ); +}; + +const loginStyles = { + fieldWrap: { + display: "flex", alignItems: "center", height: 38, + background: "rgba(255,255,255,0.95)", + border: "1px solid rgba(255,255,255,0.15)", + marginBottom: 10, + }, + fieldIcon: { width: 36, display: "inline-flex", alignItems: "center", justifyContent: "center", color: "rgba(0,0,0,0.5)" }, + fieldDivider: { width: 1, height: 18, background: "rgba(0,0,0,0.12)" }, + input: { + flex: 1, height: "100%", border: "none", background: "transparent", + paddingLeft: 10, paddingRight: 10, fontSize: 13, color: "#1a2332", + outline: "none", fontFamily: "inherit", + }, +}; + +window.Login = Login; diff --git a/prototype/src/meganav.jsx b/prototype/src/meganav.jsx new file mode 100644 index 0000000..e780209 --- /dev/null +++ b/prototype/src/meganav.jsx @@ -0,0 +1,108 @@ +// Full-viewport "全部导航" mega-menu, modeled after the reference screenshot. +const MegaNav = ({ onClose, onOpen }) => { + const [activeSection, setActiveSection] = React.useState("sys"); + const cols = XLY.MEGA_COLUMNS[activeSection]; + + return ( +
+ {/* Header */} +
+ + +
+ +
+ + {/* Body */} +
+ {/* Left rail */} +
+ {XLY.MEGA_NAV.map((s) => { + const active = s.id === activeSection; + return ( +
setActiveSection(s.id)} + style={{ + display: "flex", alignItems: "center", gap: 8, + padding: "8px 14px", cursor: "pointer", fontSize: 12, + background: active ? "var(--accent)" : "transparent", + color: active ? "#fff" : "var(--text-on-dark)", + borderLeft: active ? "3px solid #fff" : "3px solid transparent", + }} + onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = "rgba(255,255,255,0.06)"; }} + onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = "transparent"; }} + > + + {s.label} +
+ ); + })} +
+ + {/* Columns */} +
+ {cols ? ( +
+ {cols.map((col) => ( +
+
+ {col.title} +
+
+ {col.items.map((it, i) => { + const clickable = !!it.screen; + return ( +
{ onOpen(it.screen, it.label); onClose(); } : undefined} + style={{ + fontSize: 12, + color: it.featured ? "var(--accent)" : "var(--text-on-dark-muted)", + cursor: clickable ? "pointer" : "default", + display: "inline-flex", alignItems: "center", gap: 4, + padding: "1px 0", + }} + onMouseEnter={(e) => { if (clickable) e.currentTarget.style.color = "#fff"; }} + onMouseLeave={(e) => { if (clickable) e.currentTarget.style.color = it.featured ? "var(--accent)" : "var(--text-on-dark-muted)"; }} + > + {it.label} + {it.featured ? : null} +
+ ); + })} +
+
+ ))} +
+ ) : ( +
+ {XLY.MEGA_NAV.find((s) => s.id === activeSection)?.label} · 模块开发中 +
+ )} +
+
+
+ ); +}; + +window.MegaNav = MegaNav; diff --git a/prototype/src/primitives.jsx b/prototype/src/primitives.jsx new file mode 100644 index 0000000..8d16dde --- /dev/null +++ b/prototype/src/primitives.jsx @@ -0,0 +1,167 @@ +// Form primitives shared across screens. Tight, dense, faithful enterprise look. + +const fieldStyles = { + row: { display: "flex", alignItems: "center", gap: 0, minWidth: 0 }, + label: { + width: 88, flex: "none", textAlign: "right", paddingRight: 8, + color: "var(--text)", fontSize: 12, lineHeight: "26px", + whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", + }, + labelReq: { color: "var(--required)" }, + control: { + flex: 1, minWidth: 0, height: "var(--input-h)", + border: "1px solid var(--border-input)", + background: "var(--bg-input)", + padding: "0 6px", fontSize: 12, color: "var(--text)", + borderRadius: 0, + }, + controlDisabled: { background: "var(--bg-disabled)", color: "var(--text-muted)" }, + controlReq: { background: "#fff8e1" }, // matches reference: required cells get a yellow tint +}; + +const Field = ({ label, required, children, labelWidth, span = 1, valign = "center" }) => ( +
+
+ {required ? * : null} + {label}: +
+ {children} +
+); + +const Input = ({ value, onChange, disabled, required, placeholder, mono, style }) => ( + onChange(e.target.value) : undefined} + disabled={disabled} + placeholder={placeholder} + style={{ + ...fieldStyles.control, + ...(disabled ? fieldStyles.controlDisabled : {}), + ...(required && !disabled ? fieldStyles.controlReq : {}), + ...(mono ? { fontFamily: '"JetBrains Mono", Menlo, Consolas, monospace', fontSize: 11 } : {}), + ...style, + }} + /> +); + +const Select = ({ value, onChange, options, disabled, required, style }) => ( +
+ +
+ +
+
+); + +const Checkbox = ({ checked, onChange, disabled, label, size = 13 }) => ( + +); + +// Top-bar style (light blue) toolbar button — used in module config screen +const ToolbarBtnLight = ({ children, icon, onClick, disabled, primary, danger, success }) => { + const [hover, setHover] = React.useState(false); + let bg = "#fff", border = "var(--border-input)", color = "var(--text)"; + if (primary) { bg = hover ? "#3a9ae8" : "#5cabe8"; border = "#3a8ad6"; color = "#fff"; } + else if (danger) { bg = hover ? "#e85a5a" : "#f07070"; border = "#d04141"; color = "#fff"; } + else if (success) { bg = hover ? "#38b777" : "#4cc488"; border = "#27a567"; color = "#fff"; } + else if (hover && !disabled) { bg = "var(--accent-soft)"; border = "var(--accent)"; } + return ( + + ); +}; + +// Dark-band toolbar button (used in user detail, matches the dark sub-toolbar in references) +const ToolbarBtnDark = ({ children, icon, onClick, disabled, danger }) => { + const [hover, setHover] = React.useState(false); + return ( + + ); +}; + +const Divider = ({ vertical }) => + vertical + ? + :
; + +Object.assign(window, { + Field, Input, Select, Checkbox, ToolbarBtnLight, ToolbarBtnDark, Divider, fieldStyles, +}); diff --git a/prototype/src/screen-home.jsx b/prototype/src/screen-home.jsx new file mode 100644 index 0000000..e10eb89 --- /dev/null +++ b/prototype/src/screen-home.jsx @@ -0,0 +1,61 @@ +// Simple home — gives the workspace something useful when first opened. + +const Home = ({ user, onOpenScreen }) => { + const cards = [ + { id: "userlist", title: "用户列表", desc: "管理系统账号、角色与权限分组", icon: }, + { id: "module", title: "系统模块配置", desc: "配置 KPI 流程作业单与业务模块", icon: }, + ]; + const stats = [ + { label: "今日待办", value: 12, tone: "var(--accent)" }, + { label: "进行中报价", value: 47, tone: "var(--warning)" }, + { label: "本月订单", value: 286, tone: "var(--success)" }, + { label: "待审核", value: 8, tone: "var(--danger)" }, + ]; + return ( +
+
+
欢迎回来,{user || "admin"}
+
+ XLY-ERP 印刷制造管理平台 · {new Date().toLocaleDateString("zh-CN", { year: "numeric", month: "long", day: "numeric", weekday: "long" })} +
+
+
+ {stats.map((s) => ( +
+
{s.label}
+
+ {s.value} +
+
+ ))} +
+
+
+ 快捷入口 +
+
+ {cards.map((c) => ( +
onOpenScreen(c.id, c.title)} + style={{ + background: "#fff", padding: 14, + cursor: "pointer", display: "flex", alignItems: "center", gap: 10, + }} + onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-row-hover)"} + onMouseLeave={(e) => e.currentTarget.style.background = "#fff"} + > + {c.icon} +
+
{c.title}
+
{c.desc}
+
+
+ ))} +
+
+
+ ); +}; + +window.Home = Home; diff --git a/prototype/src/screen-module.jsx b/prototype/src/screen-module.jsx new file mode 100644 index 0000000..41fe514 --- /dev/null +++ b/prototype/src/screen-module.jsx @@ -0,0 +1,212 @@ +// Module configuration screen — KPI process tree on left, dense form on right. +// Form fields mirror the structural pattern in 模块 1.png but with original copy. + +const moduleSections = [ + { + title: "估价管理流程", processes: [ + { id: "01", code: "01/04", label: "【新增】新报价单", active: true }, + { id: "02", code: "02/04", label: "审核报价单->客户确认->二次确认" }, + { id: "03", code: "03/04", label: "客户确认->二次确认" }, + { id: "04", code: "04/04", label: "报价单->销售订单" }, + { id: "05", code: "04/04", label: "报价单->转拼版单" }, + { id: "06", code: "04/07", label: "主管审核报价单" }, + { id: "07", code: "05/07", label: "业务确认报价单" }, + ], + }, + { title: "订单生产流程", collapsed: true }, + { title: "自动拼版流程", collapsed: true }, + { title: "销售送货流程", collapsed: true }, + { title: "物料采购流程", collapsed: true }, + { title: "物料领用流程", collapsed: true }, + { title: "发外加工流程", collapsed: true }, + { title: "质量管理流程", collapsed: true }, + { title: "财务收付款流程", collapsed: true }, +]; + +const ModuleScreen = () => { + const [mode, setMode] = React.useState("view"); // 'view' | 'edit' + const [activeProcess] = React.useState("01"); + + const initial = { + displayCategory: "印刷业务", + deptEn: "Pricing Personnel", + deptCn: "报价员", + permissionVisible: true, + seq: "1", + saveProc: "Sp_Check_sQtt", + storageUrl: "/indexPage/quotationPackTl...", + showOpt: false, + nodes: "-1752556899000998290E", + deptId: "", + flowId: "20250303094458291296...", + uniqueId: "10125124011501607650...", + flowEnName: "", + flowReqName: "", + titleCn: "01/04【新增】新报价单", + mobileLogo: "", + nameEn: "Quotation Document", + nameNotice: "常规产品报价单据", + showCategory: "待处理", + deptManager: "报价人员", + storeProc: "Sp_Calc_sQtt", + quickShow: "", + addId: "", + modType: "quotation/quotation/quotati", + calcProc: "Sp_Quotation_CalcDataPac", + saveAfterProc: "Sp_beforeSave_sQtt", + parent: "1752562484000281572E", + }; + + const [form, setForm] = React.useState(initial); + const set = (k, v) => setForm((s) => ({ ...s, [k]: v })); + + const startEdit = () => setMode("edit"); + const save = () => { setMode("view"); }; + const cancel = () => { setForm(initial); setMode("view"); }; + + const disabled = mode === "view"; + + return ( +
+ {/* Light blue toolbar */} +
+ } primary disabled={mode === "edit"}>添加节点 + } primary disabled={mode === "edit"}>添加子节点 + } primary disabled={mode === "edit"} onClick={startEdit}>修 改 + } primary disabled={mode === "edit"}>复制节点 + } success disabled={mode !== "edit"} onClick={save}>保 存 + } disabled={mode !== "edit"} onClick={cancel}>取 消 + } danger disabled={mode === "edit"}>删除节点 +
+ } primary>生成监听器单 +
+ + {/* Body: form only (tree lives in main sidebar) */} +
+ {/* Form panel */} +
+
+ + + + + set("storeProc", v)} disabled={disabled} mono /> + + + set("modType", v)} disabled={disabled} mono /> + + + + set("deptEn", v)} disabled={disabled} /> + + + set("deptId", v)} disabled={disabled} mono /> + + + set("addId", v)} disabled={disabled} mono /> + + + +
+ set("permissionVisible", v)} disabled={disabled} /> +
+
+ + + + + set("flowReqName", v)} disabled={disabled} /> + + + + set("seq", v)} disabled={disabled} required /> + + + + + + set("calcProc", v)} disabled={disabled} mono /> + + + set("saveAfterProc", v)} disabled={disabled} mono /> + + + + set("saveProc", v)} disabled={disabled} mono /> + + + set("titleCn", v)} disabled={disabled} /> + + + set("deleteProc", v)} disabled={disabled} mono /> + + + + + + + set("storageUrl", v)} disabled={disabled} mono /> + + + set("mobileLogo", v)} disabled={disabled} /> + + + set("nameEn", v)} disabled={disabled} /> + + + set("nameNotice", v)} disabled={disabled} /> + + + +
+ set("showOpt", v)} disabled={disabled} /> +
+
+ + set("showCategory", v)} disabled={disabled} options={["待处理", "已处理", "全部"]} /> + + + + + + set("createdBy", v)} disabled={disabled} required /> + + + set("employee", v)} disabled={disabled} required /> + + + + set("account", v)} disabled={disabled} required /> + + + set("empNo", v)} disabled={disabled} required mono /> + + + + set("language", v)} disabled={disabled} required options={XLY.LANGUAGES} /> + + + setFilter(e.target.value)} + placeholder="过滤..." + style={{ height: 20, padding: "0 6px", border: "1px solid var(--border-input)", background: "#fff", fontSize: 11, width: 120, fontFamily: "inherit" }} + /> + + + + + + {XLY.PERMISSION_GROUPS.filter((g) => g !== "权限分类" && (!filter || g.includes(filter))).map((g, i) => { + const isHover = hovered === g; + return ( + setHovered(g)} onMouseLeave={() => setHovered(null)} + onClick={disabled ? undefined : () => setPerm(g, !user.permissions[g])} + style={{ + background: isHover && !disabled ? "var(--bg-row-hover)" + : user.permissions[g] ? "var(--accent-soft)" + : i % 2 === 0 ? "#fff" : "var(--bg-row-zebra)", + cursor: disabled ? "default" : "pointer", + height: 24, + }} + > + + setPerm(g, v)} + disabled={disabled} + /> + + + {g} + + + ); + })} + + +
+ ); +}; + +const ScopeTab = ({ tabKey, user, setTabPerm, disabled }) => { + const map = { + customer: { label: "客户", items: ["上海印行包装", "锐尚文创", "京华彩印", "广印纸品", "万象图文", "联合包装", "鼎盛印刷", "九洲胶印"] }, + supplier: { label: "供应商", items: ["华东油墨", "正信纸业", "宝洁化工", "日新油墨", "三鼎纸业", "鸿丰胶辊", "永利印材"] }, + staff: { label: "人员", items: ["管广飞", "李斌", "孟威", "王宽明", "潘强", "杨柳"] }, + process: { label: "工序", items: ["印前", "印刷", "覆膜", "模切", "装订", "胶装", "丝网", "烫金", "包装"] }, + driver: { label: "司机", items: ["陈师傅", "李师傅", "王师傅", "钱师傅", "赵师傅"] }, + }; + const cfg = map[tabKey]; + const [filter, setFilter] = React.useState(""); + const enabled = !!user.tabPerms[tabKey]; + + return ( +
+
+ setTabPerm(tabKey, v)} + disabled={disabled} + label={`启用${cfg.label}查看权限`} + /> + setFilter(e.target.value)} + placeholder={`筛选${cfg.label}...`} + style={{ height: 24, padding: "0 8px", border: "1px solid var(--border-input)", background: "var(--bg-input)", fontSize: 12, width: 200, fontFamily: "inherit" }} + /> + + + 共 {cfg.items.length} 个{cfg.label} + +
+
+ {cfg.items.filter((x) => !filter || x.includes(filter)).map((it) => ( + + ))} +
+
+ ); +}; + +window.UserDetail = UserDetail; diff --git a/prototype/src/screen-userlist.jsx b/prototype/src/screen-userlist.jsx new file mode 100644 index 0000000..ac4aae7 --- /dev/null +++ b/prototype/src/screen-userlist.jsx @@ -0,0 +1,216 @@ +// User list — searchable/filterable table. + +const UserList = ({ users, onOpenUser, onCreateUser }) => { + const [scope, setScope] = React.useState("all"); + const [field, setField] = React.useState("empName"); + const [matchMode, setMatchMode] = React.useState("contains"); + const [query, setQuery] = React.useState(""); + const [selected, setSelected] = React.useState(null); + const [checkedRows, setCheckedRows] = React.useState({}); + const [sort, setSort] = React.useState({ key: null, dir: "asc" }); + + const toggleRow = (id) => setCheckedRows((s) => ({ ...s, [id]: !s[id] })); + const checkedCount = Object.values(checkedRows).filter(Boolean).length; + + const filtered = React.useMemo(() => { + let rows = users; + if (scope === "active") rows = rows.filter((u) => !u.disabled); + if (scope === "disabled") rows = rows.filter((u) => u.disabled); + if (query) { + const q = query.toLowerCase(); + const cmp = (a, b) => { + if (matchMode === "exact") return a === b; + if (matchMode === "starts") return a.startsWith(b); + return a.includes(b); + }; + rows = rows.filter((u) => { + const v = String(u[field === "empName" ? "employee" : field === "account" ? "account" : field === "empNo" ? "empNo" : field === "department" ? "department" : "employee"] || "").toLowerCase(); + return cmp(v, q); + }); + } + if (sort.key) { + const dir = sort.dir === "asc" ? 1 : -1; + rows = [...rows].sort((a, b) => String(a[sort.key]).localeCompare(String(b[sort.key]), "zh") * dir); + } + return rows; + }, [users, scope, query, field, matchMode, sort]); + + const toggleSort = (k) => setSort((s) => ({ key: k, dir: s.key === k && s.dir === "asc" ? "desc" : "asc" })); + + return ( +
+ {/* Filter toolbar */} +
+ }>刷新 + } primary onClick={onCreateUser}>新增 + }>导出 Excel + + + + + setQuery(e.target.value)} + placeholder="" + style={{ height: 26, width: 180, border: "1px solid var(--border-input)", padding: "0 6px", fontSize: 12, background: "var(--bg-input)" }} + /> + } primary>查 找 + { setQuery(""); setScope("all"); }}>清 空 +
+ + {checkedCount > 0 ? <>已选 {checkedCount} 条 / : null} + 共 {users.length} 条记录 + +
+ + {/* Table */} +
+ + + + + {colDefs.map((c) => )} + + + + + + {colDefs.map((c) => ( + + ))} + + + + {filtered.map((u, idx) => { + const isSel = selected === u.id; + return ( + setSelected(u.id)} + onDoubleClick={() => onOpenUser(u)} + style={{ + background: isSel ? "var(--selected)" : idx % 2 === 0 ? "#fff" : "var(--bg-row-zebra)", + cursor: "pointer", height: 24, + color: u.disabled ? "var(--text-faint)" : "var(--text)", + }} + onMouseEnter={(e) => { if (!isSel) e.currentTarget.style.background = "var(--bg-row-hover)"; }} + onMouseLeave={(e) => { if (!isSel) e.currentTarget.style.background = idx % 2 === 0 ? "#fff" : "var(--bg-row-zebra)"; }} + > + + + + + + + + + + + + + + ); + })} + +
+ 0 && filtered.every((u) => checkedRows[u.id])} + onChange={(v) => { + const next = { ...checkedRows }; + filtered.forEach((u) => { next[u.id] = v; }); + setCheckedRows(next); + }} + /> + toggleSort("seq")}>序号 toggleSort(c.key)}> + + {c.label} + {sort.key === c.key ? ( + + {sort.dir === "asc" ? : } + + ) : ( + + )} + +
e.stopPropagation()}> + toggleRow(u.id)} /> + {u.seq}{u.employee}{u.empNo}{u.account}{u.department}{u.type}{u.language} + + {u.lastLogin}{u.createdBy}{u.createdAt}
+ {filtered.length === 0 ? ( +
+ 没有匹配的记录 +
+ ) : null} +
+ + {/* Footer */} +
+ 当前显示:共 {filtered.length} 条单据 共 {filtered.length} 条记录 +
+ + 1 + + +
+
+
+ ); +}; + +const colDefs = [ + { key: "employee", label: "员工名", w: 100 }, + { key: "empNo", label: "员工号", w: 90 }, + { key: "account", label: "用户号", w: 90 }, + { key: "department", label: "部门", w: 110 }, + { key: "type", label: "用户类型", w: 110 }, + { key: "language", label: "语言", w: 60 }, + { key: "disabled", label: "作废", w: 50 }, + { key: "lastLogin", label: "登录日期", w: 145 }, + { key: "createdBy", label: "制单人", w: 90 }, + { key: "createdAt", label: "制单日期", w: 145 }, +]; + +const selectStyle = { + height: 26, padding: "0 6px", border: "1px solid var(--border-input)", + background: "#fff", fontSize: 12, color: "var(--text)", fontFamily: "inherit", +}; + +const thStyle = { + padding: "5px 8px", textAlign: "left", fontWeight: 500, + borderRight: "1px solid var(--border)", borderBottom: "1px solid var(--border)", + whiteSpace: "nowrap", +}; + +const tdStyle = { + padding: "3px 8px", borderRight: "1px solid var(--border)", borderBottom: "1px solid #f0f2f5", + whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", +}; + +const pgBtn = { + width: 22, height: 22, border: "1px solid var(--border-input)", background: "#fff", cursor: "pointer", + display: "inline-flex", alignItems: "center", justifyContent: "center", +}; + +window.UserList = UserList; diff --git a/prototype/src/sidebar.jsx b/prototype/src/sidebar.jsx new file mode 100644 index 0000000..f89dec2 --- /dev/null +++ b/prototype/src/sidebar.jsx @@ -0,0 +1,123 @@ +// Sidebar — search + collapsible tree, dense Windows-explorer style. + +const Sidebar = ({ tree, activeNodeId, expanded, setExpanded, onNodeClick, query, setQuery }) => { + return ( +
+ {/* Search bar */} +
+
+ setQuery(e.target.value)} + placeholder="请输入您想要搜索的关键字" + style={{ + width: "100%", height: 26, paddingLeft: 8, paddingRight: 26, + border: "1px solid var(--border-input)", background: "var(--bg-input)", + fontSize: 12, color: "var(--text)", + }} + /> +
+ +
+
+
+ {/* Tree */} +
+ {tree.map((node) => ( + + ))} +
+
+ ); +}; + +const matches = (node, q) => { + if (!q) return true; + const t = q.toLowerCase(); + if ((node.label || "").toLowerCase().includes(t)) return true; + if (node.children) return node.children.some((c) => matches(c, q)); + return false; +}; + +const TreeNode = ({ node, depth, activeNodeId, expanded, setExpanded, onNodeClick, query }) => { + const hasChildren = node.children && node.children.length > 0; + const isExpanded = expanded[node.id] || (query && matches(node, query) && hasChildren); + const isActive = activeNodeId === node.id; + const visible = matches(node, query); + if (!visible) return null; + + const click = () => { + if (hasChildren) { + setExpanded((s) => ({ ...s, [node.id]: !isExpanded })); + } + if (node.leaf || !hasChildren) { + onNodeClick(node); + } + }; + + return ( +
+
{ if (!isActive) e.currentTarget.style.background = "var(--bg-row-hover)"; }} + onMouseLeave={(e) => { if (!isActive) e.currentTarget.style.background = "transparent"; }} + > + {hasChildren ? ( + + {isExpanded ? : } + + ) : ( + + + + )} + + {node.label} + +
+ {isExpanded && hasChildren ? ( +
+ {node.children.map((child) => ( + + ))} +
+ ) : null} +
+ ); +}; + +window.Sidebar = Sidebar; diff --git a/prototype/src/tabs.jsx b/prototype/src/tabs.jsx new file mode 100644 index 0000000..7972409 --- /dev/null +++ b/prototype/src/tabs.jsx @@ -0,0 +1,67 @@ +// Top-bar tab strip — IDE-style, multi-tab open/close/switch. + +const TabStrip = ({ tabs, activeTabId, onSelect, onClose }) => { + return ( +
+ {tabs.map((t) => { + const active = t.id === activeTabId; + return ; + })} +
+ ); +}; + +const Tab = ({ tab, active, onSelect, onClose }) => { + const [hover, setHover] = React.useState(false); + return ( +
onSelect(tab.id)} + onMouseEnter={() => setHover(true)} + onMouseLeave={() => setHover(false)} + style={{ + position: "relative", + display: "flex", alignItems: "center", gap: 6, + height: "100%", padding: "0 12px", + background: active ? "var(--bg-tab-active)" : hover ? "rgba(255,255,255,0.5)" : "transparent", + borderTop: active ? "2px solid var(--accent)" : "2px solid transparent", + borderLeft: "1px solid var(--border)", + borderRight: "1px solid var(--border)", + marginRight: -1, + cursor: "pointer", fontSize: 12, + color: active ? "var(--text)" : "var(--text-muted)", + fontWeight: active ? 500 : 400, + whiteSpace: "nowrap", + }} + > + {tab.label} + {tab.closable !== false ? ( + { e.stopPropagation(); onClose(tab.id); }} + style={{ + display: "inline-flex", alignItems: "center", justifyContent: "center", + width: 14, height: 14, color: "var(--text-faint)", + borderRadius: 2, + }} + onMouseEnter={(e) => { + e.currentTarget.style.background = "var(--danger)"; + e.currentTarget.style.color = "#fff"; + }} + onMouseLeave={(e) => { + e.currentTarget.style.background = "transparent"; + e.currentTarget.style.color = "var(--text-faint)"; + }} + > + + + ) : null} +
+ ); +}; + +window.TabStrip = TabStrip; diff --git a/prototype/src/workspace.jsx b/prototype/src/workspace.jsx new file mode 100644 index 0000000..6e72fd1 --- /dev/null +++ b/prototype/src/workspace.jsx @@ -0,0 +1,166 @@ +// Workspace shell: top bar + sidebar + tabs + screen routing. + +const Workspace = ({ session, onLogout }) => { + const [users, setUsers] = React.useState(() => XLY.buildUsers()); + const [expanded, setExpanded] = React.useState({ kpi: true, quote: true, sys: true }); + const [searchQ, setSearchQ] = React.useState(""); + const [activeNodeId, setActiveNodeId] = React.useState("home"); + const [sidebarOpen, setSidebarOpen] = React.useState(true); + + const [tabs, setTabs] = React.useState([ + { id: "home", label: "主页", screen: "home", icon: "home", closable: false }, + ]); + const [activeTabId, setActiveTabId] = React.useState("home"); + const [megaOpen, setMegaOpen] = React.useState(false); + + const openTab = (tab) => { + setTabs((ts) => ts.find((t) => t.id === tab.id) ? ts : [...ts, tab]); + setActiveTabId(tab.id); + }; + const closeTab = (id) => { + setTabs((ts) => { + const i = ts.findIndex((t) => t.id === id); + const next = ts.filter((t) => t.id !== id); + if (id === activeTabId) { + const fallback = next[Math.max(0, i - 1)] || next[0]; + if (fallback) setActiveTabId(fallback.id); + } + return next; + }); + }; + + const onNodeClick = (node) => { + setActiveNodeId(node.id); + if (node.screen === "userlist") openTab({ id: "userlist", label: "用户列表", screen: "userlist" }); + else if (node.screen === "module") openTab({ id: "module", label: "系统模块配置", screen: "module" }); + else if (node.id === "home") openTab({ id: "home", label: "主页", screen: "home", closable: false }); + else if (node.id === "quote-01") openTab({ id: "module", label: "系统模块配置", screen: "module" }); + else openTab({ id: node.id, label: node.label, screen: "stub", stubLabel: node.label }); + }; + + const openUserDetail = (user, mode = "view") => { + const id = `user-${user.id}`; + const label = `用户信息单据 · ${user.employee}`; + setTabs((ts) => { + if (ts.find((t) => t.id === id)) return ts; + return [...ts, { id, label, screen: "userdetail", userId: user.id, mode }]; + }); + setActiveTabId(id); + }; + const createUser = () => { + const newU = { + id: `new-${Date.now()}`, seq: users.length + 1, employee: "", empNo: "", account: "", + department: "", type: "普通用户", language: "中文", disabled: false, + lastLogin: "", createdBy: session.user, createdAt: new Date().toISOString().slice(0, 19).replace("T", " "), + permissions: XLY.PERMISSION_GROUPS.reduce((a, g) => (a[g] = false, a), {}), + tabPerms: {}, + }; + setUsers((us) => [...us, newU]); + openUserDetail(newU, "new"); + }; + + const saveUser = (u) => setUsers((us) => us.map((x) => x.id === u.id ? u : x)); + const activeTab = tabs.find((t) => t.id === activeTabId); + + return ( +
+ {/* Top bar */} +
+ + +
+ + XLY-ERP + · {session.company} +
+ + + + +
+ + {/* Tab strip */} + + + {/* Body */} +
+ {sidebarOpen && activeTabId === "home" ? ( +
+ +
+ ) : null} +
+ {activeTab ? openTab({ id: s, label, screen: s })} /> : null} +
+
+ + {/* Status bar */} +
+ 就绪 · 当前用户 {session.user} · {session.company} + XLY-ERP v8.6.2 · © 2017–2026 XLY 软件股份 +
+ {megaOpen ? ( + setMegaOpen(false)} + onOpen={(screen, label) => openTab({ id: screen, label, screen })} + /> + ) : null} +
+ ); +}; + +const ScreenRouter = ({ tab, users, onOpenUser, onCreateUser, onSaveUser, session, onOpenScreen }) => { + if (tab.screen === "home") return ; + if (tab.screen === "userlist") return ; + if (tab.screen === "module") return ; + if (tab.screen === "userdetail") { + const u = users.find((x) => x.id === tab.userId); + if (!u) return 用户不存在; + return ; + } + return {tab.stubLabel || tab.label} · 模块开发中; +}; + +const Empty = ({ children }) => ( +
+ {children} +
+); + +const topBtn = { + display: "inline-flex", alignItems: "center", gap: 5, + height: 28, padding: "0 10px", background: "transparent", + color: "var(--text-on-dark)", border: "none", cursor: "pointer", + fontSize: 12, +}; + +window.Workspace = Workspace; diff --git a/prototype/uploads/pasted-1777540186759-0.png b/prototype/uploads/pasted-1777540186759-0.png new file mode 100644 index 0000000..b8080ee --- /dev/null +++ b/prototype/uploads/pasted-1777540186759-0.png diff --git a/prototype/uploads/模块 1.png b/prototype/uploads/模块 1.png new file mode 100644 index 0000000..4f470bd --- /dev/null +++ b/prototype/uploads/模块 1.png diff --git a/prototype/uploads/用户2.png b/prototype/uploads/用户2.png new file mode 100644 index 0000000..fbca61e --- /dev/null +++ b/prototype/uploads/用户2.png diff --git a/prototype/uploads/用户修改 1.png b/prototype/uploads/用户修改 1.png new file mode 100644 index 0000000..e9cd0ea --- /dev/null +++ b/prototype/uploads/用户修改 1.png diff --git a/prototype/uploads/用户修改 2.png b/prototype/uploads/用户修改 2.png new file mode 100644 index 0000000..49378d9 --- /dev/null +++ b/prototype/uploads/用户修改 2.png diff --git a/prototype/uploads/用户查询.png b/prototype/uploads/用户查询.png new file mode 100644 index 0000000..1c55eb5 --- /dev/null +++ b/prototype/uploads/用户查询.png diff --git a/prototype/uploads/登录 1.png b/prototype/uploads/登录 1.png new file mode 100644 index 0000000..4c2e6bb --- /dev/null +++ b/prototype/uploads/登录 1.png diff --git a/prototype/uploads/登录 2.png b/prototype/uploads/登录 2.png new file mode 100644 index 0000000..6fbfbab --- /dev/null +++ b/prototype/uploads/登录 2.png diff --git a/scripts/seed-dev-admin.sh b/scripts/seed-dev-admin.sh new file mode 100755 index 0000000..aaff654 --- /dev/null +++ b/scripts/seed-dev-admin.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Seed a dev admin user (sUserName=admin, password=666666). +# +# Prerequisites: +# 1. .env.local exists with DB_HOST/DB_PORT/DB_SCHEMA/DB_USER/DB_PASSWORD +# 2. Schema has been created — i.e. Spring Boot has started at least once +# so Flyway has applied V1__initial_schema.sql (creates tUser etc.) +# +# Idempotent: uses INSERT IGNORE on the unique key (sUserName). + +set -euo pipefail + +# macOS Homebrew mysql-client is keg-only; prepend common install paths. +for p in /opt/homebrew/opt/mysql-client/bin /usr/local/opt/mysql-client/bin; do + [ -d "$p" ] && case ":$PATH:" in *":$p:"*) ;; *) PATH="$p:$PATH" ;; esac +done + +ENV_FILE="$(dirname "$0")/../.env.local" +[ -f "$ENV_FILE" ] || { echo "[seed-dev-admin] ⚠️ .env.local 不存在($ENV_FILE)" >&2; exit 1; } + +set -a; . "$ENV_FILE"; set +a + +# BCrypt(666666) cost 10. Spring's BCryptPasswordEncoder accepts both $2a$ and $2b$ prefixes. +HASH='$2b$10$ZzbGP0yWo3QJkaiZqEgtAOQVrzrti911VqbY7FoethhGQFPV0/Oj6' + +echo "[seed-dev-admin] target: ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_SCHEMA}" + +mysql -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASSWORD}" "${DB_SCHEMA}" <&2; exit 1; } + +set -a; . "$ENV_FILE"; set +a + +# BCrypt(666666) cost 10. Spring's BCryptPasswordEncoder accepts $2a$/$2b$. +HASH='$2b$10$ZzbGP0yWo3QJkaiZqEgtAOQVrzrti911VqbY7FoethhGQFPV0/Oj6' + +echo "[seed-dev-data] target: ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_SCHEMA}" + +mysql -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASSWORD}" "${DB_SCHEMA}" <客户确认', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_QuoteFlow') AS x),2,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_QuoteCustConfirm','单据','Pricing',1,'03/04 客户确认->二次确认', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_QuoteFlow') AS x),3,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_QuoteToOrder','单据','Pricing',1,'04/04 报价单->销售订单', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_QuoteFlow') AS x),4,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_QuoteToImpose','单据','Pricing',1,'04/04 报价单->转拼版单', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_QuoteFlow') AS x),5,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_QuoteSupervisorAudit','单据','Pricing',1,'04/07 主管审核报价单', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_QuoteFlow') AS x),6,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_QuoteSalesConfirm','单据','Pricing',1,'05/07 业务确认报价单', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_QuoteFlow') AS x),7,'admin',0); + +-- 2. 订单生产流程 +INSERT IGNORE INTO tModule (sBrandsId,sSubsidiaryId,tCreateDate,sDisplayType,sProcedureName,sModuleType,sManageDeptEn,bShowPermission,sModuleNameZh,iParentId,iSortOrder,sCreatedBy,bDeleted) VALUES +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_OrderFlow','KPI流程','Production',1,'订单生产流程',NULL,2,'admin',0); +INSERT IGNORE INTO tModule (sBrandsId,sSubsidiaryId,tCreateDate,sDisplayType,sProcedureName,sModuleType,sManageDeptEn,bShowPermission,sModuleNameZh,iParentId,iSortOrder,sCreatedBy,bDeleted) VALUES +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_OrderCreate','单据','Production',1,'01/03 销售订单->生产任务单', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_OrderFlow') AS x),1,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_OrderDispatch','单据','Production',1,'02/03 任务单分派', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_OrderFlow') AS x),2,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_OrderComplete','单据','Production',1,'03/03 任务单完成', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_OrderFlow') AS x),3,'admin',0); + +-- 3. 自动拼版流程 +INSERT IGNORE INTO tModule (sBrandsId,sSubsidiaryId,tCreateDate,sDisplayType,sProcedureName,sModuleType,sManageDeptEn,bShowPermission,sModuleNameZh,iParentId,iSortOrder,sCreatedBy,bDeleted) VALUES +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_PanelFlow','KPI流程','Imposition',1,'自动拼版流程',NULL,3,'admin',0); +INSERT IGNORE INTO tModule (sBrandsId,sSubsidiaryId,tCreateDate,sDisplayType,sProcedureName,sModuleType,sManageDeptEn,bShowPermission,sModuleNameZh,iParentId,iSortOrder,sCreatedBy,bDeleted) VALUES +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_PanelCreate','单据','Imposition',1,'01/02 拼版任务生成', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_PanelFlow') AS x),1,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_PanelAudit','单据','Imposition',1,'02/02 拼版审核', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_PanelFlow') AS x),2,'admin',0); + +-- 4. 销售送货流程 +INSERT IGNORE INTO tModule (sBrandsId,sSubsidiaryId,tCreateDate,sDisplayType,sProcedureName,sModuleType,sManageDeptEn,bShowPermission,sModuleNameZh,iParentId,iSortOrder,sCreatedBy,bDeleted) VALUES +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_ShipFlow','KPI流程','Logistics',1,'销售送货流程',NULL,4,'admin',0); +INSERT IGNORE INTO tModule (sBrandsId,sSubsidiaryId,tCreateDate,sDisplayType,sProcedureName,sModuleType,sManageDeptEn,bShowPermission,sModuleNameZh,iParentId,iSortOrder,sCreatedBy,bDeleted) VALUES +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_ShipCreate','单据','Logistics',1,'01/02 发货单创建', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_ShipFlow') AS x),1,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_ShipConfirm','单据','Logistics',1,'02/02 签收确认', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_ShipFlow') AS x),2,'admin',0); + +-- 5. 物料采购流程 +INSERT IGNORE INTO tModule (sBrandsId,sSubsidiaryId,tCreateDate,sDisplayType,sProcedureName,sModuleType,sManageDeptEn,bShowPermission,sModuleNameZh,iParentId,iSortOrder,sCreatedBy,bDeleted) VALUES +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_PurchFlow','KPI流程','Purchasing',1,'物料采购流程',NULL,5,'admin',0); +INSERT IGNORE INTO tModule (sBrandsId,sSubsidiaryId,tCreateDate,sDisplayType,sProcedureName,sModuleType,sManageDeptEn,bShowPermission,sModuleNameZh,iParentId,iSortOrder,sCreatedBy,bDeleted) VALUES +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_PurchRequest','单据','Purchasing',1,'01/03 采购申请', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_PurchFlow') AS x),1,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_PurchOrder','单据','Purchasing',1,'02/03 采购订单', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_PurchFlow') AS x),2,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','前端业务','Sp_PurchReceive','单据','Purchasing',1,'03/03 入库确认', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_PurchFlow') AS x),3,'admin',0); + +-- 6. 系统设置 +INSERT IGNORE INTO tModule (sBrandsId,sSubsidiaryId,tCreateDate,sDisplayType,sProcedureName,sModuleType,sManageDeptEn,bShowPermission,sModuleNameZh,iParentId,iSortOrder,sCreatedBy,bDeleted) VALUES +('XLY','XLY','2024-01-10 08:00:00','系统配置','Sp_SysFlow','系统','SysAdmin',0,'系统设置',NULL,99,'admin',0); +INSERT IGNORE INTO tModule (sBrandsId,sSubsidiaryId,tCreateDate,sDisplayType,sProcedureName,sModuleType,sManageDeptEn,bShowPermission,sModuleNameZh,iParentId,iSortOrder,sCreatedBy,bDeleted) VALUES +('XLY','XLY','2024-01-10 08:00:00','系统配置','Sp_SysUserList','单据','SysAdmin',0,'用户列表', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_SysFlow') AS x),1,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','系统配置','Sp_SysModuleConfig','单据','SysAdmin',0,'系统模块配置', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_SysFlow') AS x),2,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','系统配置','Sp_SysRole','单据','SysAdmin',0,'角色管理', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_SysFlow') AS x),3,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','系统配置','Sp_SysMenu','单据','SysAdmin',0,'菜单配置', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_SysFlow') AS x),4,'admin',0), +('XLY','XLY','2024-01-10 08:00:00','系统配置','Sp_SysLog','单据','SysAdmin',0,'操作日志', (SELECT iIncrement FROM (SELECT iIncrement FROM tModule WHERE sProcedureName='Sp_SysFlow') AS x),5,'admin',0); + +SELECT + (SELECT COUNT(*) FROM tStaff) AS staff_rows, + (SELECT COUNT(*) FROM tUser) AS user_rows, + (SELECT COUNT(*) FROM tPermissionCategory) AS perm_cat_rows, + (SELECT COUNT(*) FROM tUserPermission) AS user_perm_rows, + (SELECT COUNT(*) FROM tModule) AS module_rows; +SQL + +echo "[seed-dev-data] OK" diff --git a/sql/migrations/V2__bool_columns_to_bit.sql b/sql/migrations/V2__bool_columns_to_bit.sql new file mode 100644 index 0000000..5da81b2 --- /dev/null +++ b/sql/migrations/V2__bool_columns_to_bit.sql @@ -0,0 +1,24 @@ +-- Flyway migration V2 — switch boolean flag columns from TINYINT(1) to BIT(1). +-- +-- Why: BIT(1) gives strict 0/1 storage at the type level. MySQL Connector/J 8.x +-- maps BIT(1) ↔ Boolean automatically (no driver param needed). MyBatis-Plus +-- entity fields stay `Boolean`, no Java code changes required. Existing queries +-- like `WHERE bDeleted = 0` continue to work because MySQL implicitly converts +-- integer literals to bit values. +-- +-- ALTER TABLE on a populated table preserves data: TINYINT 0 → BIT b'0', +-- TINYINT 1 → BIT b'1'. Indexes (e.g. idx_deleted_login) survive the modify. + +ALTER TABLE `tUser` + MODIFY COLUMN `bCanModifyDocs` BIT(1) NOT NULL DEFAULT b'0' COMMENT '单据修改权限;0 否 / 1 是', + MODIFY COLUMN `bDeleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '软删除标记;0 有效 / 1 已作废'; + +ALTER TABLE `tStaff` + MODIFY COLUMN `bDeleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '软删除标记'; + +ALTER TABLE `tPermissionCategory` + MODIFY COLUMN `bDeleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '软删除标记'; + +ALTER TABLE `tModule` + MODIFY COLUMN `bShowPermission` BIT(1) NOT NULL DEFAULT b'0' COMMENT '权限是否显示;0 否 / 1 是', + MODIFY COLUMN `bDeleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '软删除标记';